diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2019-04-07 21:50:36 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-04-07 21:50:36 +0900 |
| commit | f0a29721c9fb10f97faf386bc9d6b1b2fad97895 (patch) | |
| tree | b5c1d38d698589bb444c0881a431391db91eb5bc /src/server | |
| parent | Update README.md [AUTOGEN] (#4639) (diff) | |
| download | sharkey-f0a29721c9fb10f97faf386bc9d6b1b2fad97895.tar.gz sharkey-f0a29721c9fb10f97faf386bc9d6b1b2fad97895.tar.bz2 sharkey-f0a29721c9fb10f97faf386bc9d6b1b2fad97895.zip | |
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
* :v:
* wip
* Update note.ts
* Update postgre.ts
* wip
* wip
* wip
* wip
* Update add-file.ts
* wip
* wip
* wip
* Clean up
* Update logs.ts
* wip
* :pizza:
* 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
* :v:
* clean up
* docs
* Update push.ts
* wip
* Update api.ts
* wip
* :v:
* Update make-pagination-query.ts
* :v:
* 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
* :art:
* 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
* :v:
* 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
* :art:
* 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
* :v:
* Refactoring
#1540
* test
* test
* test
* test
* test
* test
* test
* Fix bug
* Fix test
* :sushi:
* wip
* #4471
* Add test for #4335
* Refactor
* Fix test
* Add tests
* :clock4:
* Fix bug
* Add test
* Add test
* rename
* Fix bug
Diffstat (limited to 'src/server')
223 files changed, 2873 insertions, 5070 deletions
diff --git a/src/server/activitypub.ts b/src/server/activitypub.ts index df5f5b141d..3b39977d47 100644 --- a/src/server/activitypub.ts +++ b/src/server/activitypub.ts @@ -1,15 +1,11 @@ -import { ObjectID } from 'mongodb'; import * as Router from 'koa-router'; import * as json from 'koa-json-body'; import * as httpSignature from 'http-signature'; import { renderActivity } from '../remote/activitypub/renderer'; -import Note from '../models/note'; -import User, { isLocalUser, ILocalUser, IUser } from '../models/user'; -import Emoji from '../models/emoji'; import renderNote from '../remote/activitypub/renderer/note'; import renderKey from '../remote/activitypub/renderer/key'; -import renderPerson from '../remote/activitypub/renderer/person'; +import { renderPerson } from '../remote/activitypub/renderer/person'; import renderEmoji from '../remote/activitypub/renderer/emoji'; import Outbox, { packActivity } from './activitypub/outbox'; import Followers from './activitypub/followers'; @@ -18,6 +14,9 @@ import Featured from './activitypub/featured'; import renderQuestion from '../remote/activitypub/renderer/question'; import { inbox as processInbox } from '../queue'; import { isSelfHost } from '../misc/convert-host'; +import { Notes, Users, Emojis, UserKeypairs, Polls } from '../models'; +import { ILocalUser, User } from '../models/entities/user'; +import { In } from 'typeorm'; // Init router const router = new Router(); @@ -64,25 +63,20 @@ router.post('/users/:user/inbox', json(), inbox); router.get('/notes/:note', async (ctx, next) => { if (!isActivityPubReq(ctx)) return await next(); - if (!ObjectID.isValid(ctx.params.note)) { - ctx.status = 404; - return; - } - - const note = await Note.findOne({ - _id: new ObjectID(ctx.params.note), - visibility: { $in: ['public', 'home'] }, - localOnly: { $ne: true } + const note = await Notes.findOne({ + id: ctx.params.note, + visibility: In(['public', 'home']), + localOnly: false }); - if (note === null) { + if (note == null) { ctx.status = 404; return; } // リモートだったらリダイレクト - if (note._user.host != null) { - if (note.uri == null || isSelfHost(note._user.host)) { + if (note.userHost != null) { + if (note.uri == null || isSelfHost(note.userHost)) { ctx.status = 500; return; } @@ -97,19 +91,14 @@ router.get('/notes/:note', async (ctx, next) => { // note activity router.get('/notes/:note/activity', async ctx => { - if (!ObjectID.isValid(ctx.params.note)) { - ctx.status = 404; - return; - } - - const note = await Note.findOne({ - _id: new ObjectID(ctx.params.note), - '_user.host': null, - visibility: { $in: ['public', 'home'] }, - localOnly: { $ne: true } + const note = await Notes.findOne({ + id: ctx.params.note, + userHost: null, + visibility: In(['public', 'home']), + localOnly: false }); - if (note === null) { + if (note == null) { ctx.status = 404; return; } @@ -121,32 +110,23 @@ router.get('/notes/:note/activity', async ctx => { // question router.get('/questions/:question', async (ctx, next) => { - if (!ObjectID.isValid(ctx.params.question)) { - ctx.status = 404; - return; - } - - const poll = await Note.findOne({ - _id: new ObjectID(ctx.params.question), - '_user.host': null, - visibility: { $in: ['public', 'home'] }, - localOnly: { $ne: true }, - poll: { - $exists: true, - $ne: null - }, + const pollNote = await Notes.findOne({ + id: ctx.params.question, + userHost: null, + visibility: In(['public', 'home']), + localOnly: false, + hasPoll: true }); - if (poll === null) { + if (pollNote == null) { ctx.status = 404; return; } - const user = await User.findOne({ - _id: poll.userId - }); + const user = await Users.findOne(pollNote.userId); + const poll = await Polls.findOne({ noteId: pollNote.id }); - ctx.body = renderActivity(await renderQuestion(user as ILocalUser, poll)); + ctx.body = renderActivity(await renderQuestion(user as ILocalUser, pollNote, poll)); setResponseType(ctx); }); @@ -164,25 +144,24 @@ router.get('/users/:user/collections/featured', Featured); // publickey router.get('/users/:user/publickey', async ctx => { - if (!ObjectID.isValid(ctx.params.user)) { - ctx.status = 404; - return; - } - - const userId = new ObjectID(ctx.params.user); + const userId = ctx.params.user; - const user = await User.findOne({ - _id: userId, + const user = await Users.findOne({ + id: userId, host: null }); - if (user === null) { + if (user == null) { ctx.status = 404; return; } - if (isLocalUser(user)) { - ctx.body = renderActivity(renderKey(user)); + const keypair = await UserKeypairs.findOne({ + userId: user.id + }); + + if (Users.isLocalUser(user)) { + ctx.body = renderActivity(renderKey(user, keypair)); ctx.set('Cache-Control', 'public, max-age=180'); setResponseType(ctx); } else { @@ -191,8 +170,8 @@ router.get('/users/:user/publickey', async ctx => { }); // user -async function userInfo(ctx: Router.IRouterContext, user: IUser) { - if (user === null) { +async function userInfo(ctx: Router.IRouterContext, user: User) { + if (user == null) { ctx.status = 404; return; } @@ -205,15 +184,10 @@ async function userInfo(ctx: Router.IRouterContext, user: IUser) { router.get('/users/:user', async (ctx, next) => { if (!isActivityPubReq(ctx)) return await next(); - if (!ObjectID.isValid(ctx.params.user)) { - ctx.status = 404; - return; - } - - const userId = new ObjectID(ctx.params.user); + const userId = ctx.params.user; - const user = await User.findOne({ - _id: userId, + const user = await Users.findOne({ + id: userId, host: null }); @@ -223,7 +197,7 @@ router.get('/users/:user', async (ctx, next) => { router.get('/@:user', async (ctx, next) => { if (!isActivityPubReq(ctx)) return await next(); - const user = await User.findOne({ + const user = await Users.findOne({ usernameLower: ctx.params.user.toLowerCase(), host: null }); @@ -234,12 +208,12 @@ router.get('/@:user', async (ctx, next) => { // emoji router.get('/emojis/:emoji', async ctx => { - const emoji = await Emoji.findOne({ + const emoji = await Emojis.findOne({ host: null, name: ctx.params.emoji }); - if (emoji === null) { + if (emoji == null) { ctx.status = 404; return; } diff --git a/src/server/activitypub/featured.ts b/src/server/activitypub/featured.ts index fc6150902b..f43312d79a 100644 --- a/src/server/activitypub/featured.ts +++ b/src/server/activitypub/featured.ts @@ -1,35 +1,28 @@ -import { ObjectID } from 'mongodb'; import * as Router from 'koa-router'; import config from '../../config'; -import User from '../../models/user'; import { renderActivity } from '../../remote/activitypub/renderer'; import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection'; import { setResponseType } from '../activitypub'; -import Note from '../../models/note'; import renderNote from '../../remote/activitypub/renderer/note'; +import { Users, Notes, UserNotePinings } from '../../models'; export default async (ctx: Router.IRouterContext) => { - if (!ObjectID.isValid(ctx.params.user)) { - ctx.status = 404; - return; - } - - const userId = new ObjectID(ctx.params.user); + const userId = ctx.params.user; // Verify user - const user = await User.findOne({ - _id: userId, + const user = await Users.findOne({ + id: userId, host: null }); - if (user === null) { + if (user == null) { ctx.status = 404; return; } - const pinnedNoteIds = user.pinnedNoteIds || []; + const pinings = await UserNotePinings.find({ userId: user.id }); - const pinnedNotes = await Promise.all(pinnedNoteIds.filter(ObjectID.isValid).map(id => Note.findOne({ _id: id }))); + const pinnedNotes = await Promise.all(pinings.map(pining => Notes.findOne(pining.noteId))); const renderedNotes = await Promise.all(pinnedNotes.map(note => renderNote(note))); diff --git a/src/server/activitypub/followers.ts b/src/server/activitypub/followers.ts index 002576b2e7..62c54399ed 100644 --- a/src/server/activitypub/followers.ts +++ b/src/server/activitypub/followers.ts @@ -1,24 +1,18 @@ -import { ObjectID } from 'mongodb'; import * as Router from 'koa-router'; import config from '../../config'; import $ from 'cafy'; -import ID, { transform } from '../../misc/cafy-id'; -import User from '../../models/user'; -import Following from '../../models/following'; +import { ID } from '../../misc/cafy-id'; import * as url from '../../prelude/url'; import { renderActivity } from '../../remote/activitypub/renderer'; import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection'; import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page'; import renderFollowUser from '../../remote/activitypub/renderer/follow-user'; import { setResponseType } from '../activitypub'; +import { Users, Followings } from '../../models'; +import { LessThan } from 'typeorm'; export default async (ctx: Router.IRouterContext) => { - if (!ObjectID.isValid(ctx.params.user)) { - ctx.status = 404; - return; - } - - const userId = new ObjectID(ctx.params.user); + const userId = ctx.params.user; // Get 'cursor' parameter const [cursor, cursorErr] = $.optional.type(ID).get(ctx.request.query.cursor); @@ -34,12 +28,12 @@ export default async (ctx: Router.IRouterContext) => { } // Verify user - const user = await User.findOne({ - _id: userId, + const user = await Users.findOne({ + id: userId, host: null }); - if (user === null) { + if (user == null) { ctx.status = 404; return; } @@ -49,22 +43,20 @@ export default async (ctx: Router.IRouterContext) => { if (page) { const query = { - followeeId: user._id + followeeId: user.id } as any; // カーソルが指定されている場合 if (cursor) { - query._id = { - $lt: transform(cursor) - }; + query.id = LessThan(cursor); } // Get followers - const followings = await Following - .find(query, { - limit: limit + 1, - sort: { _id: -1 } - }); + const followings = await Followings.find({ + where: query, + take: limit + 1, + order: { id: -1 } + }); // 「次のページ」があるかどうか const inStock = followings.length === limit + 1; @@ -80,7 +72,7 @@ export default async (ctx: Router.IRouterContext) => { null, inStock ? `${partOf}?${url.query({ page: 'true', - cursor: followings[followings.length - 1]._id.toHexString() + cursor: followings[followings.length - 1].id })}` : null ); diff --git a/src/server/activitypub/following.ts b/src/server/activitypub/following.ts index 0d7486f68a..4894aac1f8 100644 --- a/src/server/activitypub/following.ts +++ b/src/server/activitypub/following.ts @@ -1,24 +1,19 @@ -import { ObjectID } from 'mongodb'; import * as Router from 'koa-router'; import config from '../../config'; import $ from 'cafy'; -import ID, { transform } from '../../misc/cafy-id'; -import User from '../../models/user'; -import Following from '../../models/following'; +import { ID } from '../../misc/cafy-id'; import * as url from '../../prelude/url'; import { renderActivity } from '../../remote/activitypub/renderer'; import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection'; import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page'; import renderFollowUser from '../../remote/activitypub/renderer/follow-user'; import { setResponseType } from '../activitypub'; +import { Users, Followings } from '../../models'; +import { LessThan, FindConditions } from 'typeorm'; +import { Following } from '../../models/entities/following'; export default async (ctx: Router.IRouterContext) => { - if (!ObjectID.isValid(ctx.params.user)) { - ctx.status = 404; - return; - } - - const userId = new ObjectID(ctx.params.user); + const userId = ctx.params.user; // Get 'cursor' parameter const [cursor, cursorErr] = $.optional.type(ID).get(ctx.request.query.cursor); @@ -34,12 +29,12 @@ export default async (ctx: Router.IRouterContext) => { } // Verify user - const user = await User.findOne({ - _id: userId, + const user = await Users.findOne({ + id: userId, host: null }); - if (user === null) { + if (user == null) { ctx.status = 404; return; } @@ -49,22 +44,20 @@ export default async (ctx: Router.IRouterContext) => { if (page) { const query = { - followerId: user._id - } as any; + followerId: user.id + } as FindConditions<Following>; // カーソルが指定されている場合 if (cursor) { - query._id = { - $lt: transform(cursor) - }; + query.id = LessThan(cursor); } // Get followings - const followings = await Following - .find(query, { - limit: limit + 1, - sort: { _id: -1 } - }); + const followings = await Followings.find({ + where: query, + take: limit + 1, + order: { id: -1 } + }); // 「次のページ」があるかどうか const inStock = followings.length === limit + 1; @@ -80,7 +73,7 @@ export default async (ctx: Router.IRouterContext) => { null, inStock ? `${partOf}?${url.query({ page: 'true', - cursor: followings[followings.length - 1]._id.toHexString() + cursor: followings[followings.length - 1].id })}` : null ); diff --git a/src/server/activitypub/outbox.ts b/src/server/activitypub/outbox.ts index ff8f884b19..377f43c986 100644 --- a/src/server/activitypub/outbox.ts +++ b/src/server/activitypub/outbox.ts @@ -1,28 +1,23 @@ -import { ObjectID } from 'mongodb'; import * as Router from 'koa-router'; import config from '../../config'; import $ from 'cafy'; -import ID, { transform } from '../../misc/cafy-id'; -import User from '../../models/user'; +import { ID } from '../../misc/cafy-id'; import { renderActivity } from '../../remote/activitypub/renderer'; import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection'; import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page'; import { setResponseType } from '../activitypub'; - -import Note, { INote } from '../../models/note'; import renderNote from '../../remote/activitypub/renderer/note'; import renderCreate from '../../remote/activitypub/renderer/create'; import renderAnnounce from '../../remote/activitypub/renderer/announce'; import { countIf } from '../../prelude/array'; import * as url from '../../prelude/url'; +import { Users, Notes } from '../../models'; +import { makePaginationQuery } from '../api/common/make-pagination-query'; +import { Brackets } from 'typeorm'; +import { Note } from '../../models/entities/note'; export default async (ctx: Router.IRouterContext) => { - if (!ObjectID.isValid(ctx.params.user)) { - ctx.status = 404; - return; - } - - const userId = new ObjectID(ctx.params.user); + const userId = ctx.params.user; // Get 'sinceId' parameter const [sinceId, sinceIdErr] = $.optional.type(ID).get(ctx.request.query.since_id); @@ -41,12 +36,12 @@ export default async (ctx: Router.IRouterContext) => { } // Verify user - const user = await User.findOne({ - _id: userId, + const user = await Users.findOne({ + id: userId, host: null }); - if (user === null) { + if (user == null) { ctx.status = 404; return; } @@ -55,34 +50,15 @@ export default async (ctx: Router.IRouterContext) => { const partOf = `${config.url}/users/${userId}/outbox`; if (page) { - //#region Construct query - const sort = { - _id: -1 - }; - - const query = { - userId: user._id, - visibility: { $in: ['public', 'home'] }, - localOnly: { $ne: true } - } as any; - - if (sinceId) { - sort._id = 1; - query._id = { - $gt: transform(sinceId) - }; - } else if (untilId) { - query._id = { - $lt: transform(untilId) - }; - } - //#endregion + const query = makePaginationQuery(Notes.createQueryBuilder('note'), sinceId, untilId) + .andWhere('note.userId = :userId', { userId: user.id }) + .andWhere(new Brackets(qb => { qb + .where(`note.visibility = 'public'`) + .orWhere(`note.visibility = 'home'`); + })) + .andWhere('note.localOnly = FALSE'); - const notes = await Note - .find(query, { - limit: limit, - sort: sort - }); + const notes = await query.take(limit).getMany(); if (sinceId) notes.reverse(); @@ -96,11 +72,11 @@ export default async (ctx: Router.IRouterContext) => { user.notesCount, activities, partOf, notes.length ? `${partOf}?${url.query({ page: 'true', - since_id: notes[0]._id.toHexString() + since_id: notes[0].id })}` : null, notes.length ? `${partOf}?${url.query({ page: 'true', - until_id: notes[notes.length - 1]._id.toHexString() + until_id: notes[notes.length - 1].id })}` : null ); @@ -123,10 +99,10 @@ export default async (ctx: Router.IRouterContext) => { * Pack Create<Note> or Announce Activity * @param note Note */ -export async function packActivity(note: INote): Promise<object> { - if (note.renoteId && note.text == null && note.poll == null && (note.fileIds == null || note.fileIds.length == 0)) { - const renote = await Note.findOne(note.renoteId); - return renderAnnounce(renote.uri ? renote.uri : `${config.url}/notes/${renote._id}`, note); +export async function packActivity(note: Note): Promise<object> { + if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length == 0)) { + const renote = await Notes.findOne(note.renoteId); + return renderAnnounce(renote.uri ? renote.uri : `${config.url}/notes/${renote.id}`, note); } return renderCreate(await renderNote(note, false), note); diff --git a/src/server/api/authenticate.ts b/src/server/api/authenticate.ts index 7781b87c88..e293e3fed0 100644 --- a/src/server/api/authenticate.ts +++ b/src/server/api/authenticate.ts @@ -1,37 +1,37 @@ -import App, { IApp } from '../../models/app'; -import { default as User, IUser } from '../../models/user'; -import AccessToken from '../../models/access-token'; import isNativeToken from './common/is-native-token'; +import { User } from '../../models/entities/user'; +import { App } from '../../models/entities/app'; +import { Users, AccessTokens, Apps } from '../../models'; -export default async (token: string): Promise<[IUser, IApp]> => { +export default async (token: string): Promise<[User, App]> => { if (token == null) { return [null, null]; } if (isNativeToken(token)) { // Fetch user - const user: IUser = await User + const user = await Users .findOne({ token }); - if (user === null) { + if (user == null) { throw 'user not found'; } return [user, null]; } else { - const accessToken = await AccessToken.findOne({ + const accessToken = await AccessTokens.findOne({ hash: token.toLowerCase() }); - if (accessToken === null) { + if (accessToken == null) { throw 'invalid signature'; } - const app = await App - .findOne({ _id: accessToken.appId }); + const app = await Apps + .findOne(accessToken.appId); - const user = await User - .findOne({ _id: accessToken.userId }); + const user = await Users + .findOne(accessToken.userId); return [user, app]; } diff --git a/src/server/api/call.ts b/src/server/api/call.ts index 89a44b3c65..885c622667 100644 --- a/src/server/api/call.ts +++ b/src/server/api/call.ts @@ -1,10 +1,10 @@ import { performance } from 'perf_hooks'; import limiter from './limiter'; -import { IUser } from '../../models/user'; -import { IApp } from '../../models/app'; +import { User } from '../../models/entities/user'; import endpoints from './endpoints'; import { ApiError } from './error'; import { apiLogger } from './logger'; +import { App } from '../../models/entities/app'; const accessDenied = { message: 'Access denied.', @@ -12,7 +12,7 @@ const accessDenied = { id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e' }; -export default async (endpoint: string, user: IUser, app: IApp, data: any, file?: any) => { +export default async (endpoint: string, user: User, app: App, data: any, file?: any) => { const isSecure = user != null && app == null; const ep = endpoints.find(e => e.name === endpoint); diff --git a/src/server/api/common/generate-mute-query.ts b/src/server/api/common/generate-mute-query.ts new file mode 100644 index 0000000000..090c14eb83 --- /dev/null +++ b/src/server/api/common/generate-mute-query.ts @@ -0,0 +1,36 @@ +import { User } from '../../../models/entities/user'; +import { Mutings } from '../../../models'; +import { SelectQueryBuilder, Brackets } from 'typeorm'; + +export function generateMuteQuery(q: SelectQueryBuilder<any>, me: User) { + const mutingQuery = Mutings.createQueryBuilder('muting') + .select('muting.muteeId') + .where('muting.muterId = :muterId', { muterId: me.id }); + + // 投稿の作者をミュートしていない かつ + // 投稿の返信先の作者をミュートしていない かつ + // 投稿の引用元の作者をミュートしていない + q + .andWhere(`note.userId NOT IN (${ mutingQuery.getQuery() })`) + .andWhere(new Brackets(qb => { qb + .where(`note.replyUserId IS NULL`) + .orWhere(`note.replyUserId NOT IN (${ mutingQuery.getQuery() })`); + })) + .andWhere(new Brackets(qb => { qb + .where(`note.renoteUserId IS NULL`) + .orWhere(`note.renoteUserId NOT IN (${ mutingQuery.getQuery() })`); + })); + + q.setParameters(mutingQuery.getParameters()); +} + +export function generateMuteQueryForUsers(q: SelectQueryBuilder<any>, me: User) { + const mutingQuery = Mutings.createQueryBuilder('muting') + .select('muting.muteeId') + .where('muting.muterId = :muterId', { muterId: me.id }); + + q + .andWhere(`user.id NOT IN (${ mutingQuery.getQuery() })`); + + q.setParameters(mutingQuery.getParameters()); +} diff --git a/src/server/api/common/generate-native-user-token.ts b/src/server/api/common/generate-native-user-token.ts index 2082b89a5a..92f8a3a0e8 100644 --- a/src/server/api/common/generate-native-user-token.ts +++ b/src/server/api/common/generate-native-user-token.ts @@ -1,3 +1,3 @@ import rndstr from 'rndstr'; -export default () => `!${rndstr('a-zA-Z0-9', 32)}`; +export default () => `!${rndstr('a-zA-Z0-9', 31)}`; diff --git a/src/server/api/common/generate-visibility-query.ts b/src/server/api/common/generate-visibility-query.ts new file mode 100644 index 0000000000..2807dc99dc --- /dev/null +++ b/src/server/api/common/generate-visibility-query.ts @@ -0,0 +1,40 @@ +import { User } from '../../../models/entities/user'; +import { Followings } from '../../../models'; +import { Brackets, SelectQueryBuilder } from 'typeorm'; + +export function generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: User) { + if (me == null) { + q.andWhere(new Brackets(qb => { qb + .where(`note.visibility = 'public'`) + .orWhere(`note.visibility = 'home'`); + })); + } else { + const followingQuery = Followings.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: me.id }); + + q.andWhere(new Brackets(qb => { qb + // 公開投稿である + .where(new Brackets(qb => { qb + .where(`note.visibility = 'public'`) + .orWhere(`note.visibility = 'home'`); + })) + // または 自分自身 + .orWhere('note.userId = :userId1', { userId1: me.id }) + // または 自分宛て + .orWhere(':userId2 = ANY(note.visibleUserIds)', { userId2: me.id }) + .orWhere(new Brackets(qb => { qb + // または フォロワー宛ての投稿であり、 + .where('note.visibility = \'followers\'') + .andWhere(new Brackets(qb => { qb + // 自分がフォロワーである + .where(`note.userId IN (${ followingQuery.getQuery() })`) + // または 自分の投稿へのリプライ + .orWhere('note.replyUserId = :userId3', { userId3: me.id }); + })); + })); + })); + + q.setParameters(followingQuery.getParameters()); + } +} diff --git a/src/server/api/common/get-friends.ts b/src/server/api/common/get-friends.ts deleted file mode 100644 index 876aa399f7..0000000000 --- a/src/server/api/common/get-friends.ts +++ /dev/null @@ -1,49 +0,0 @@ -import * as mongodb from 'mongodb'; -import Following from '../../../models/following'; - -export const getFriendIds = async (me: mongodb.ObjectID, includeMe = true) => { - // Fetch relation to other users who the I follows - // SELECT followee - const followings = await Following - .find({ - followerId: me - }, { - fields: { - followeeId: true - } - }); - - // ID list of other users who the I follows - const myfollowingIds = followings.map(following => following.followeeId); - - if (includeMe) { - myfollowingIds.push(me); - } - - return myfollowingIds; -}; - -export const getFriends = async (me: mongodb.ObjectID, includeMe = true, remoteOnly = false) => { - const q: any = remoteOnly ? { - followerId: me, - '_followee.host': { $ne: null } - } : { - followerId: me - }; - // Fetch relation to other users who the I follows - const followings = await Following - .find(q); - - // ID list of other users who the I follows - const myfollowings = followings.map(following => ({ - id: following.followeeId - })); - - if (includeMe) { - myfollowings.push({ - id: me - }); - } - - return myfollowings; -}; diff --git a/src/server/api/common/get-hide-users.ts b/src/server/api/common/get-hide-users.ts deleted file mode 100644 index 3cdf806751..0000000000 --- a/src/server/api/common/get-hide-users.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as mongo from 'mongodb'; -import Mute from '../../../models/mute'; -import User, { IUser } from '../../../models/user'; -import { unique } from '../../../prelude/array'; - -export async function getHideUserIds(me: IUser) { - return await getHideUserIdsById(me ? me._id : null); -} - -export async function getHideUserIdsById(meId?: mongo.ObjectID) { - const [suspended, muted] = await Promise.all([ - User.find({ - isSuspended: true - }, { - fields: { - _id: true - } - }), - meId ? Mute.find({ - muterId: meId - }) : Promise.resolve([]) - ]); - - return unique(suspended.map(user => user._id).concat(muted.map(mute => mute.muteeId))); -} diff --git a/src/server/api/common/getters.ts b/src/server/api/common/getters.ts index 7a72e6489a..b720840ebb 100644 --- a/src/server/api/common/getters.ts +++ b/src/server/api/common/getters.ts @@ -1,18 +1,15 @@ -import * as mongo from 'mongodb'; -import Note from '../../../models/note'; -import User, { isRemoteUser, isLocalUser } from '../../../models/user'; import { IdentifiableError } from '../../../misc/identifiable-error'; +import { User } from '../../../models/entities/user'; +import { Note } from '../../../models/entities/note'; +import { Notes, Users } from '../../../models'; /** * Get note for API processing */ -export async function getNote(noteId: mongo.ObjectID) { - const note = await Note.findOne({ - _id: noteId, - deletedAt: { $exists: false } - }); +export async function getNote(noteId: Note['id']) { + const note = await Notes.findOne(noteId); - if (note === null) { + if (note == null) { throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); } @@ -22,23 +19,10 @@ export async function getNote(noteId: mongo.ObjectID) { /** * Get user for API processing */ -export async function getUser(userId: mongo.ObjectID) { - const user = await User.findOne({ - _id: userId, - $or: [{ - isDeleted: { $exists: false } - }, { - isDeleted: false - }] - }, { - fields: { - data: false, - profile: false, - clientSettings: false - } - }); +export async function getUser(userId: User['id']) { + const user = await Users.findOne(userId); - if (user === null) { + if (user == null) { throw new IdentifiableError('15348ddd-432d-49c2-8a5a-8069753becff', 'No such user.'); } @@ -48,10 +32,10 @@ export async function getUser(userId: mongo.ObjectID) { /** * Get remote user for API processing */ -export async function getRemoteUser(userId: mongo.ObjectID) { +export async function getRemoteUser(userId: User['id']) { const user = await getUser(userId); - if (!isRemoteUser(user)) { + if (!Users.isRemoteUser(user)) { throw 'user is not a remote user'; } @@ -61,10 +45,10 @@ export async function getRemoteUser(userId: mongo.ObjectID) { /** * Get local user for API processing */ -export async function getLocalUser(userId: mongo.ObjectID) { +export async function getLocalUser(userId: User['id']) { const user = await getUser(userId); - if (!isLocalUser(user)) { + if (!Users.isLocalUser(user)) { throw 'user is not a local user'; } diff --git a/src/server/api/common/make-pagination-query.ts b/src/server/api/common/make-pagination-query.ts new file mode 100644 index 0000000000..0c859a4f8d --- /dev/null +++ b/src/server/api/common/make-pagination-query.ts @@ -0,0 +1,28 @@ +import { SelectQueryBuilder } from 'typeorm'; + +export function makePaginationQuery<T>(q: SelectQueryBuilder<T>, sinceId: string, untilId: string, sinceDate?: number, untilDate?: number) { + if (sinceId && untilId) { + q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId }); + q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId }); + q.orderBy(`${q.alias}.id`, 'DESC'); + } else if (sinceId) { + q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId }); + q.orderBy(`${q.alias}.id`, 'ASC'); + } else if (untilId) { + q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId }); + q.orderBy(`${q.alias}.id`, 'DESC'); + } else if (sinceDate && untilDate) { + q.andWhere(`${q.alias}.createdAt > :sinceDate`, { sinceDate: new Date(sinceDate) }); + q.andWhere(`${q.alias}.createdAt < :untilDate`, { untilDate: new Date(untilDate) }); + q.orderBy(`${q.alias}.createdAt`, 'DESC'); + } else if (sinceDate) { + q.andWhere(`${q.alias}.createdAt > :sinceDate`, { sinceDate: new Date(sinceDate) }); + q.orderBy(`${q.alias}.createdAt`, 'ASC'); + } else if (untilDate) { + q.andWhere(`${q.alias}.createdAt < :untilDate`, { untilDate: new Date(untilDate) }); + q.orderBy(`${q.alias}.createdAt`, 'DESC'); + } else { + q.orderBy(`${q.alias}.id`, 'DESC'); + } + return q; +} diff --git a/src/server/api/common/read-messaging-message.ts b/src/server/api/common/read-messaging-message.ts index 9f1e7e6ab4..2cb5a1f87f 100644 --- a/src/server/api/common/read-messaging-message.ts +++ b/src/server/api/common/read-messaging-message.ts @@ -1,77 +1,43 @@ -import * as mongo from 'mongodb'; -import isObjectId from '../../../misc/is-objectid'; -import Message from '../../../models/messaging-message'; -import { IMessagingMessage as IMessage } from '../../../models/messaging-message'; import { publishMainStream } from '../../../services/stream'; import { publishMessagingStream } from '../../../services/stream'; import { publishMessagingIndexStream } from '../../../services/stream'; -import User from '../../../models/user'; +import { User } from '../../../models/entities/user'; +import { MessagingMessage } from '../../../models/entities/messaging-message'; +import { MessagingMessages } from '../../../models'; +import { In } from 'typeorm'; /** * Mark messages as read */ -export default ( - user: string | mongo.ObjectID, - otherparty: string | mongo.ObjectID, - message: string | string[] | IMessage | IMessage[] | mongo.ObjectID | mongo.ObjectID[] -) => new Promise<any>(async (resolve, reject) => { - - const userId = isObjectId(user) - ? user - : new mongo.ObjectID(user); - - const otherpartyId = isObjectId(otherparty) - ? otherparty - : new mongo.ObjectID(otherparty); - - const ids: mongo.ObjectID[] = Array.isArray(message) - ? isObjectId(message[0]) - ? (message as mongo.ObjectID[]) - : typeof message[0] === 'string' - ? (message as string[]).map(m => new mongo.ObjectID(m)) - : (message as IMessage[]).map(m => m._id) - : isObjectId(message) - ? [(message as mongo.ObjectID)] - : typeof message === 'string' - ? [new mongo.ObjectID(message)] - : [(message as IMessage)._id]; +export default async ( + userId: User['id'], + otherpartyId: User['id'], + messageIds: MessagingMessage['id'][] +) => { + if (messageIds.length === 0) return; // Update documents - await Message.update({ - _id: { $in: ids }, + await MessagingMessages.update({ + id: In(messageIds), userId: otherpartyId, recipientId: userId, isRead: false }, { - $set: { - isRead: true - } - }, { - multi: true - }); + isRead: true + }); // Publish event - publishMessagingStream(otherpartyId, userId, 'read', ids.map(id => id.toString())); - publishMessagingIndexStream(userId, 'read', ids.map(id => id.toString())); + publishMessagingStream(otherpartyId, userId, 'read', messageIds); + publishMessagingIndexStream(userId, 'read', messageIds); // Calc count of my unread messages - const count = await Message - .count({ - recipientId: userId, - isRead: false - }, { - limit: 1 - }); + const count = await MessagingMessages.count({ + recipientId: userId, + isRead: false + }); if (count == 0) { - // Update flag - User.update({ _id: userId }, { - $set: { - hasUnreadMessagingMessage: false - } - }); - // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行 publishMainStream(userId, 'readAllMessagingMessages'); } -}); +}; diff --git a/src/server/api/common/read-notification.ts b/src/server/api/common/read-notification.ts index 4361305119..c8d43ba286 100644 --- a/src/server/api/common/read-notification.ts +++ b/src/server/api/common/read-notification.ts @@ -1,72 +1,38 @@ -import * as mongo from 'mongodb'; -import isObjectId from '../../../misc/is-objectid'; -import { default as Notification, INotification } from '../../../models/notification'; import { publishMainStream } from '../../../services/stream'; -import Mute from '../../../models/mute'; -import User from '../../../models/user'; +import { User } from '../../../models/entities/user'; +import { Notification } from '../../../models/entities/notification'; +import { Mutings, Notifications } from '../../../models'; +import { In, Not } from 'typeorm'; /** * Mark notifications as read */ -export default ( - user: string | mongo.ObjectID, - message: string | string[] | INotification | INotification[] | mongo.ObjectID | mongo.ObjectID[] -) => new Promise<any>(async (resolve, reject) => { - - const userId = isObjectId(user) - ? user - : new mongo.ObjectID(user); - - const ids: mongo.ObjectID[] = Array.isArray(message) - ? isObjectId(message[0]) - ? (message as mongo.ObjectID[]) - : typeof message[0] === 'string' - ? (message as string[]).map(m => new mongo.ObjectID(m)) - : (message as INotification[]).map(m => m._id) - : isObjectId(message) - ? [(message as mongo.ObjectID)] - : typeof message === 'string' - ? [new mongo.ObjectID(message)] - : [(message as INotification)._id]; - - const mute = await Mute.find({ +export async function readNotification( + userId: User['id'], + notificationIds: Notification['id'][] +) { + const mute = await Mutings.find({ muterId: userId }); const mutedUserIds = mute.map(m => m.muteeId); // Update documents - await Notification.update({ - _id: { $in: ids }, + await Notifications.update({ + id: In(notificationIds), isRead: false }, { - $set: { - isRead: true - } - }, { - multi: true - }); + isRead: true + }); // Calc count of my unread notifications - const count = await Notification - .count({ - notifieeId: userId, - notifierId: { - $nin: mutedUserIds - }, - isRead: false - }, { - limit: 1 - }); - - if (count == 0) { - // Update flag - User.update({ _id: userId }, { - $set: { - hasUnreadNotification: false - } - }); + const count = await Notifications.count({ + notifieeId: userId, + ...(mutedUserIds.length > 0 ? { notifierId: Not(In(mutedUserIds)) } : {}), + isRead: false + }); + if (count === 0) { // 全ての(いままで未読だった)通知を(これで)読みましたよというイベントを発行 publishMainStream(userId, 'readAllNotifications'); } -}); +} diff --git a/src/server/api/common/signin.ts b/src/server/api/common/signin.ts index 84cad3a935..0f4ee4ca11 100644 --- a/src/server/api/common/signin.ts +++ b/src/server/api/common/signin.ts @@ -1,7 +1,7 @@ import * as Koa from 'koa'; import config from '../../../config'; -import { ILocalUser } from '../../../models/user'; +import { ILocalUser } from '../../../models/entities/user'; export default function(ctx: Koa.BaseContext, user: ILocalUser, redirect = false) { if (redirect) { diff --git a/src/server/api/define.ts b/src/server/api/define.ts index f2fababc32..a18419bcf6 100644 --- a/src/server/api/define.ts +++ b/src/server/api/define.ts @@ -1,8 +1,8 @@ import * as fs from 'fs'; -import { ILocalUser } from '../../models/user'; -import { IApp } from '../../models/app'; +import { ILocalUser } from '../../models/entities/user'; import { IEndpointMeta } from './endpoints'; import { ApiError } from './error'; +import { App } from '../../models/entities/app'; type Params<T extends IEndpointMeta> = { [P in keyof T['params']]: T['params'][P]['transform'] extends Function @@ -12,8 +12,8 @@ type Params<T extends IEndpointMeta> = { export type Response = Record<string, any> | void; -export default function <T extends IEndpointMeta>(meta: T, cb: (params: Params<T>, user: ILocalUser, app: IApp, file?: any, cleanup?: Function) => Promise<Response>): (params: any, user: ILocalUser, app: IApp, file?: any) => Promise<any> { - return (params: any, user: ILocalUser, app: IApp, file?: any) => { +export default function <T extends IEndpointMeta>(meta: T, cb: (params: Params<T>, user: ILocalUser, app: App, file?: any, cleanup?: Function) => Promise<Response>): (params: any, user: ILocalUser, app: App, file?: any) => Promise<any> { + return (params: any, user: ILocalUser, app: App, file?: any) => { function cleanup() { fs.unlink(file.path, () => {}); } diff --git a/src/server/api/endpoints/admin/abuse-user-reports.ts b/src/server/api/endpoints/admin/abuse-user-reports.ts index d9fe3429ce..5c5a734c1d 100644 --- a/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Report, { packMany } from '../../../../models/abuse-user-report'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; +import { AbuseUserReports } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['admin'], @@ -17,37 +18,18 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, } }; export default define(meta, async (ps) => { - const sort = { - _id: -1 - }; - const query = {} as any; - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } + const query = makePaginationQuery(AbuseUserReports.createQueryBuilder('report'), ps.sinceId, ps.untilId); - const reports = await Report - .find(query, { - limit: ps.limit, - sort: sort - }); + const reports = await query.take(ps.limit).getMany(); - return await packMany(reports); + return await AbuseUserReports.packMany(reports); }); diff --git a/src/server/api/endpoints/admin/drive/files.ts b/src/server/api/endpoints/admin/drive/files.ts index 8ed417a429..1ccabc92d9 100644 --- a/src/server/api/endpoints/admin/drive/files.ts +++ b/src/server/api/endpoints/admin/drive/files.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import File, { packMany } from '../../../../../models/drive-file'; import define from '../../../define'; import { fallback } from '../../../../../prelude/symbol'; +import { DriveFiles } from '../../../../../models'; export const meta = { tags: ['admin'], @@ -41,27 +41,25 @@ export const meta = { }; const sort: any = { // < https://github.com/Microsoft/TypeScript/issues/1863 - '+createdAt': { uploadDate: -1 }, - '-createdAt': { uploadDate: 1 }, - '+size': { length: -1 }, - '-size': { length: 1 }, - [fallback]: { _id: -1 } + '+createdAt': { createdAt: -1 }, + '-createdAt': { createdAt: 1 }, + '+size': { size: -1 }, + '-size': { size: 1 }, + [fallback]: { id: -1 } }; export default define(meta, async (ps, me) => { - const q = { - 'metadata.deletedAt': { $exists: false }, - } as any; + const q = {} as any; - if (ps.origin == 'local') q['metadata._user.host'] = null; - if (ps.origin == 'remote') q['metadata._user.host'] = { $ne: null }; + if (ps.origin == 'local') q['userHost'] = null; + if (ps.origin == 'remote') q['userHost'] = { $ne: null }; - const files = await File - .find(q, { - limit: ps.limit, - sort: sort[ps.sort] || sort[fallback], - skip: ps.offset - }); + const files = await DriveFiles.find({ + where: q, + take: ps.limit, + order: sort[ps.sort] || sort[fallback], + skip: ps.offset + }); - return await packMany(files, { detail: true, withUser: true, self: true }); + return await DriveFiles.packMany(files, { detail: true, withUser: true, self: true }); }); diff --git a/src/server/api/endpoints/admin/drive/show-file.ts b/src/server/api/endpoints/admin/drive/show-file.ts index 405b6d44ce..a2b6c158f0 100644 --- a/src/server/api/endpoints/admin/drive/show-file.ts +++ b/src/server/api/endpoints/admin/drive/show-file.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; -import DriveFile from '../../../../../models/drive-file'; import { ApiError } from '../../../error'; +import { DriveFiles } from '../../../../../models'; export const meta = { tags: ['admin'], @@ -13,7 +13,6 @@ export const meta = { params: { fileId: { validator: $.type(ID), - transform: transform, }, }, @@ -27,9 +26,7 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const file = await DriveFile.findOne({ - _id: ps.fileId - }); + const file = await DriveFiles.findOne(ps.fileId); if (file == null) { throw new ApiError(meta.errors.noSuchFile); diff --git a/src/server/api/endpoints/admin/emoji/add.ts b/src/server/api/endpoints/admin/emoji/add.ts index c126c8380f..c26e8dd04d 100644 --- a/src/server/api/endpoints/admin/emoji/add.ts +++ b/src/server/api/endpoints/admin/emoji/add.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import Emoji from '../../../../../models/emoji'; import define from '../../../define'; import { detectUrlMine } from '../../../../../misc/detect-url-mine'; +import { Emojis } from '../../../../../models'; +import { genId } from '../../../../../misc/gen-id'; export const meta = { desc: { @@ -32,7 +33,8 @@ export const meta = { export default define(meta, async (ps) => { const type = await detectUrlMine(ps.url); - const emoji = await Emoji.insert({ + const emoji = await Emojis.save({ + id: genId(), updatedAt: new Date(), name: ps.name, host: null, @@ -42,6 +44,6 @@ export default define(meta, async (ps) => { }); return { - id: emoji._id + id: emoji.id }; }); diff --git a/src/server/api/endpoints/admin/emoji/list.ts b/src/server/api/endpoints/admin/emoji/list.ts index 954f8f96c6..07174723b9 100644 --- a/src/server/api/endpoints/admin/emoji/list.ts +++ b/src/server/api/endpoints/admin/emoji/list.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; -import Emoji from '../../../../../models/emoji'; import define from '../../../define'; +import { Emojis } from '../../../../../models'; export const meta = { desc: { @@ -21,12 +21,12 @@ export const meta = { }; export default define(meta, async (ps) => { - const emojis = await Emoji.find({ + const emojis = await Emojis.find({ host: ps.host }); return emojis.map(e => ({ - id: e._id, + id: e.id, name: e.name, aliases: e.aliases, host: e.host, diff --git a/src/server/api/endpoints/admin/emoji/remove.ts b/src/server/api/endpoints/admin/emoji/remove.ts index 4c69dffbae..316834b884 100644 --- a/src/server/api/endpoints/admin/emoji/remove.ts +++ b/src/server/api/endpoints/admin/emoji/remove.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import Emoji from '../../../../../models/emoji'; import define from '../../../define'; -import ID from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; +import { Emojis } from '../../../../../models'; export const meta = { desc: { @@ -21,13 +21,9 @@ export const meta = { }; export default define(meta, async (ps) => { - const emoji = await Emoji.findOne({ - _id: ps.id - }); + const emoji = await Emojis.findOne(ps.id); if (emoji == null) throw new Error('emoji not found'); - await Emoji.remove({ _id: emoji._id }); - - return; + await Emojis.delete(emoji.id); }); diff --git a/src/server/api/endpoints/admin/emoji/update.ts b/src/server/api/endpoints/admin/emoji/update.ts index 8b1c07be9e..48b4a4ee23 100644 --- a/src/server/api/endpoints/admin/emoji/update.ts +++ b/src/server/api/endpoints/admin/emoji/update.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; -import Emoji from '../../../../../models/emoji'; import define from '../../../define'; -import ID from '../../../../../misc/cafy-id'; import { detectUrlMine } from '../../../../../misc/detect-url-mine'; +import { ID } from '../../../../../misc/cafy-id'; +import { Emojis } from '../../../../../models'; export const meta = { desc: { @@ -34,23 +34,17 @@ export const meta = { }; export default define(meta, async (ps) => { - const emoji = await Emoji.findOne({ - _id: ps.id - }); + const emoji = await Emojis.findOne(ps.id); if (emoji == null) throw new Error('emoji not found'); const type = await detectUrlMine(ps.url); - await Emoji.update({ _id: emoji._id }, { - $set: { - updatedAt: new Date(), - name: ps.name, - aliases: ps.aliases, - url: ps.url, - type, - } + await Emojis.update(emoji.id, { + updatedAt: new Date(), + name: ps.name, + aliases: ps.aliases, + url: ps.url, + type, }); - - return; }); diff --git a/src/server/api/endpoints/admin/federation/remove-all-following.ts b/src/server/api/endpoints/admin/federation/remove-all-following.ts index 98afdfc2a5..fca76e7086 100644 --- a/src/server/api/endpoints/admin/federation/remove-all-following.ts +++ b/src/server/api/endpoints/admin/federation/remove-all-following.ts @@ -1,8 +1,7 @@ import $ from 'cafy'; import define from '../../../define'; -import Following from '../../../../../models/following'; -import User from '../../../../../models/user'; import deleteFollowing from '../../../../../services/following/delete'; +import { Followings, Users } from '../../../../../models'; export const meta = { tags: ['admin'], @@ -18,13 +17,13 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const followings = await Following.find({ - '_follower.host': ps.host + const followings = await Followings.find({ + followerHost: ps.host }); const pairs = await Promise.all(followings.map(f => Promise.all([ - User.findOne({ _id: f.followerId }), - User.findOne({ _id: f.followeeId }) + Users.findOne(f.followerId), + Users.findOne(f.followeeId) ]))); for (const pair of pairs) { diff --git a/src/server/api/endpoints/admin/federation/update-instance.ts b/src/server/api/endpoints/admin/federation/update-instance.ts index 0d127b53b3..d1abe95a5b 100644 --- a/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/src/server/api/endpoints/admin/federation/update-instance.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../../define'; -import Instance from '../../../../../models/instance'; +import { Instances } from '../../../../../models'; export const meta = { tags: ['admin'], @@ -13,10 +13,6 @@ export const meta = { validator: $.str }, - isBlocked: { - validator: $.bool - }, - isClosed: { validator: $.bool }, @@ -24,18 +20,13 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const instance = await Instance.findOne({ host: ps.host }); + const instance = await Instances.findOne({ host: ps.host }); if (instance == null) { throw new Error('instance not found'); } - Instance.update({ host: ps.host }, { - $set: { - isBlocked: ps.isBlocked, - isMarkedAsClosed: ps.isClosed - } + Instances.update({ host: ps.host }, { + isMarkedAsClosed: ps.isClosed }); - - return; }); diff --git a/src/server/api/endpoints/admin/invite.ts b/src/server/api/endpoints/admin/invite.ts index 28aa301957..4e264feef6 100644 --- a/src/server/api/endpoints/admin/invite.ts +++ b/src/server/api/endpoints/admin/invite.ts @@ -1,6 +1,7 @@ import rndstr from 'rndstr'; -import RegistrationTicket from '../../../../models/registration-tickets'; import define from '../../define'; +import { RegistrationTickets } from '../../../../models'; +import { genId } from '../../../../misc/gen-id'; export const meta = { desc: { @@ -18,7 +19,8 @@ export const meta = { export default define(meta, async (ps) => { const code = rndstr({ length: 5, chars: '0-9' }); - await RegistrationTicket.insert({ + await RegistrationTickets.save({ + id: genId(), createdAt: new Date(), code: code }); diff --git a/src/server/api/endpoints/admin/logs.ts b/src/server/api/endpoints/admin/logs.ts index 805a42b9e0..eee56a3939 100644 --- a/src/server/api/endpoints/admin/logs.ts +++ b/src/server/api/endpoints/admin/logs.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import Log from '../../../../models/log'; +import { Logs } from '../../../../models'; +import { Brackets } from 'typeorm'; export const meta = { tags: ['admin'], @@ -27,41 +28,44 @@ export const meta = { }; export default define(meta, async (ps) => { - const sort = { - _id: -1 - }; - const query = {} as any; + const query = Logs.createQueryBuilder('log'); + + if (ps.level) query.andWhere('log.level = :level', { level: ps.level }); - if (ps.level) query.level = ps.level; if (ps.domain) { - for (const d of ps.domain.split(' ')) { - const qs: any[] = []; - let i = 0; - for (const sd of (d.startsWith('-') ? d.substr(1) : d).split('.')) { - qs.push({ - [`domain.${i}`]: d.startsWith('-') ? { $ne: sd } : sd - }); - i++; - } - if (d.startsWith('-')) { - if (query['$and'] == null) query['$and'] = []; - query['$and'].push({ - $and: qs - }); - } else { - if (query['$or'] == null) query['$or'] = []; - query['$or'].push({ - $and: qs - }); - } + const whiteDomains = ps.domain.split(' ').filter(x => !x.startsWith('-')); + const blackDomains = ps.domain.split(' ').filter(x => x.startsWith('-')); + + if (whiteDomains.length > 0) { + query.andWhere(new Brackets(qb => { + for (const whiteDomain of whiteDomains) { + let i = 0; + for (const subDomain of whiteDomain.split('.')) { + const p = `whiteSubDomain_${subDomain}_${i}`; + // SQL is 1 based, so we need '+ 1' + qb.orWhere(`log.domain[${i + 1}] = :${p}`, { [p]: subDomain }); + i++; + } + } + })); + } + + if (blackDomains.length > 0) { + query.andWhere(new Brackets(qb => { + for (const blackDomain of blackDomains) { + let i = 0; + for (const subDomain of blackDomain.split('.')) { + const p = `blackSubDomain_${subDomain}_${i}`; + // SQL is 1 based, so we need '+ 1' + qb.andWhere(`log.domain[${i + 1}] != :${p}`, { [p]: subDomain }); + i++; + } + } + })); } } - const logs = await Log - .find(query, { - limit: ps.limit, - sort: sort - }); + const logs = await query.take(ps.limit).getMany(); return logs; }); diff --git a/src/server/api/endpoints/admin/moderators/add.ts b/src/server/api/endpoints/admin/moderators/add.ts index 2271bcd1a9..a15f0a17a2 100644 --- a/src/server/api/endpoints/admin/moderators/add.ts +++ b/src/server/api/endpoints/admin/moderators/add.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; -import User from '../../../../../models/user'; +import { Users } from '../../../../../models'; export const meta = { desc: { @@ -17,7 +17,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID' @@ -27,21 +26,13 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); } - await User.update({ - _id: user._id - }, { - $set: { - isModerator: true - } + await Users.update(user.id, { + isModerator: true }); - - return; }); diff --git a/src/server/api/endpoints/admin/moderators/remove.ts b/src/server/api/endpoints/admin/moderators/remove.ts index 84143d3e35..209cf0814f 100644 --- a/src/server/api/endpoints/admin/moderators/remove.ts +++ b/src/server/api/endpoints/admin/moderators/remove.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; -import User from '../../../../../models/user'; +import { Users } from '../../../../../models'; export const meta = { desc: { @@ -17,7 +17,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID' @@ -27,21 +26,13 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); } - await User.update({ - _id: user._id - }, { - $set: { - isModerator: false - } + await Users.update(user.id, { + isModerator: false }); - - return; }); diff --git a/src/server/api/endpoints/admin/remove-abuse-user-report.ts b/src/server/api/endpoints/admin/remove-abuse-user-report.ts index fa17e2c937..f293c00718 100644 --- a/src/server/api/endpoints/admin/remove-abuse-user-report.ts +++ b/src/server/api/endpoints/admin/remove-abuse-user-report.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import AbuseUserReport from '../../../../models/abuse-user-report'; +import { AbuseUserReports } from '../../../../models'; export const meta = { tags: ['admin'], @@ -12,23 +12,16 @@ export const meta = { params: { reportId: { validator: $.type(ID), - transform: transform }, } }; export default define(meta, async (ps) => { - const report = await AbuseUserReport.findOne({ - _id: ps.reportId - }); + const report = await AbuseUserReports.findOne(ps.reportId); if (report == null) { throw new Error('report not found'); } - await AbuseUserReport.remove({ - _id: report._id - }); - - return; + await AbuseUserReports.delete(report.id); }); diff --git a/src/server/api/endpoints/admin/reset-password.ts b/src/server/api/endpoints/admin/reset-password.ts index 73901d8358..07b8b6d938 100644 --- a/src/server/api/endpoints/admin/reset-password.ts +++ b/src/server/api/endpoints/admin/reset-password.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User from '../../../../models/user'; import * as bcrypt from 'bcryptjs'; import rndstr from 'rndstr'; +import { Users } from '../../../../models'; export const meta = { desc: { @@ -18,7 +18,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to suspend' @@ -28,9 +27,7 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); @@ -45,12 +42,8 @@ export default define(meta, async (ps) => { // Generate hash of password const hash = bcrypt.hashSync(passwd); - await User.findOneAndUpdate({ - _id: user._id - }, { - $set: { - password: hash - } + await Users.update(user.id, { + password: hash }); return { diff --git a/src/server/api/endpoints/admin/show-user.ts b/src/server/api/endpoints/admin/show-user.ts index 985f71a873..452125dea0 100644 --- a/src/server/api/endpoints/admin/show-user.ts +++ b/src/server/api/endpoints/admin/show-user.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User from '../../../../models/user'; +import { Users } from '../../../../models'; export const meta = { desc: { @@ -16,7 +16,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to suspend' @@ -26,9 +25,7 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); diff --git a/src/server/api/endpoints/admin/show-users.ts b/src/server/api/endpoints/admin/show-users.ts index 5feb1b4fd8..73976b9872 100644 --- a/src/server/api/endpoints/admin/show-users.ts +++ b/src/server/api/endpoints/admin/show-users.ts @@ -1,7 +1,6 @@ import $ from 'cafy'; -import User, { pack } from '../../../../models/user'; import define from '../../define'; -import { fallback } from '../../../../prelude/symbol'; +import { Users } from '../../../../models'; export const meta = { tags: ['admin'], @@ -55,51 +54,38 @@ export const meta = { } }; -const sort: any = { // < https://github.com/Microsoft/TypeScript/issues/1863 - '+follower': { followersCount: -1 }, - '-follower': { followersCount: 1 }, - '+createdAt': { createdAt: -1 }, - '-createdAt': { createdAt: 1 }, - '+updatedAt': { updatedAt: -1 }, - '-updatedAt': { updatedAt: 1 }, - [fallback]: { _id: -1 } -}; - export default define(meta, async (ps, me) => { - const q = { - $and: [] - } as any; + const query = Users.createQueryBuilder('user'); + + switch (ps.state) { + case 'admin': query.where('user.isAdmin = TRUE'); break; + case 'moderator': query.where('user.isModerator = TRUE'); break; + case 'adminOrModerator': query.where('user.isAdmin = TRUE OR isModerator = TRUE'); break; + case 'verified': query.where('user.isVerified = TRUE'); break; + case 'alive': query.where('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break; + case 'silenced': query.where('user.isSilenced = TRUE'); break; + case 'suspended': query.where('user.isSuspended = TRUE'); break; + } - // state - q.$and.push( - ps.state == 'admin' ? { isAdmin: true } : - ps.state == 'moderator' ? { isModerator: true } : - ps.state == 'adminOrModerator' ? { - $or: [{ - isAdmin: true - }, { - isModerator: true - }] - } : - ps.state == 'verified' ? { isVerified: true } : - ps.state == 'silenced' ? { isSilenced: true } : - ps.state == 'suspended' ? { isSuspended: true } : - {} - ); + switch (ps.origin) { + case 'local': query.andWhere('user.host IS NULL'); break; + case 'remote': query.andWhere('user.host IS NOT NULL'); break; + } + + switch (ps.sort) { + case '+follower': query.orderBy('user.followersCount', 'DESC'); break; + case '-follower': query.orderBy('user.followersCount', 'ASC'); break; + case '+createdAt': query.orderBy('user.createdAt', 'DESC'); break; + case '-createdAt': query.orderBy('user.createdAt', 'ASC'); break; + case '+updatedAt': query.orderBy('user.updatedAt', 'DESC'); break; + case '-updatedAt': query.orderBy('user.updatedAt', 'ASC'); break; + default: query.orderBy('user.id', 'ASC'); break; + } - // origin - q.$and.push( - ps.origin == 'local' ? { host: null } : - ps.origin == 'remote' ? { host: { $ne: null } } : - {} - ); + query.take(ps.limit); + query.skip(ps.offset); - const users = await User - .find(q, { - limit: ps.limit, - sort: sort[ps.sort] || sort[fallback], - skip: ps.offset - }); + const users = await query.getMany(); - return await Promise.all(users.map(user => pack(user, me, { detail: true }))); + return await Users.packMany(users, me, { detail: true }); }); diff --git a/src/server/api/endpoints/admin/silence-user.ts b/src/server/api/endpoints/admin/silence-user.ts index 2557d8de6a..83aa88012a 100644 --- a/src/server/api/endpoints/admin/silence-user.ts +++ b/src/server/api/endpoints/admin/silence-user.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User from '../../../../models/user'; +import { Users } from '../../../../models'; export const meta = { desc: { @@ -17,7 +17,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to make silence' @@ -27,9 +26,7 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); @@ -39,13 +36,7 @@ export default define(meta, async (ps) => { throw new Error('cannot silence admin'); } - await User.findOneAndUpdate({ - _id: user._id - }, { - $set: { - isSilenced: true - } + await Users.update(user.id, { + isSilenced: true }); - - return; }); diff --git a/src/server/api/endpoints/admin/suspend-user.ts b/src/server/api/endpoints/admin/suspend-user.ts index 0a2d309530..fa4d378708 100644 --- a/src/server/api/endpoints/admin/suspend-user.ts +++ b/src/server/api/endpoints/admin/suspend-user.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User, { IUser } from '../../../../models/user'; -import Following from '../../../../models/following'; import deleteFollowing from '../../../../services/following/delete'; +import { Users, Followings } from '../../../../models'; +import { User } from '../../../../models/entities/user'; export const meta = { desc: { @@ -19,7 +19,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to suspend' @@ -29,9 +28,7 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); @@ -45,27 +42,21 @@ export default define(meta, async (ps) => { throw new Error('cannot suspend moderator'); } - await User.findOneAndUpdate({ - _id: user._id - }, { - $set: { - isSuspended: true - } + await Users.update(user.id, { + isSuspended: true }); unFollowAll(user); - - return; }); -async function unFollowAll(follower: IUser) { - const followings = await Following.find({ - followerId: follower._id +async function unFollowAll(follower: User) { + const followings = await Followings.find({ + followerId: follower.id }); for (const following of followings) { - const followee = await User.findOne({ - _id: following.followeeId + const followee = await Users.findOne({ + id: following.followeeId }); if (followee == null) { diff --git a/src/server/api/endpoints/admin/unsilence-user.ts b/src/server/api/endpoints/admin/unsilence-user.ts index 01bf41aaef..f9b173366b 100644 --- a/src/server/api/endpoints/admin/unsilence-user.ts +++ b/src/server/api/endpoints/admin/unsilence-user.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User from '../../../../models/user'; +import { Users } from '../../../../models'; export const meta = { desc: { @@ -17,7 +17,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to unsilence' @@ -27,21 +26,13 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); } - await User.findOneAndUpdate({ - _id: user._id - }, { - $set: { - isSilenced: false - } + await Users.update(user.id, { + isSilenced: false }); - - return; }); diff --git a/src/server/api/endpoints/admin/unsuspend-user.ts b/src/server/api/endpoints/admin/unsuspend-user.ts index 5da35f28e6..08dae034d3 100644 --- a/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/src/server/api/endpoints/admin/unsuspend-user.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User from '../../../../models/user'; +import { Users } from '../../../../models'; export const meta = { desc: { @@ -17,7 +17,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to unsuspend' @@ -27,21 +26,13 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); } - await User.findOneAndUpdate({ - _id: user._id - }, { - $set: { - isSuspended: false - } + await Users.update(user.id, { + isSuspended: false }); - - return; }); diff --git a/src/server/api/endpoints/admin/unverify-user.ts b/src/server/api/endpoints/admin/unverify-user.ts index d3ca05cb39..b215dbf10d 100644 --- a/src/server/api/endpoints/admin/unverify-user.ts +++ b/src/server/api/endpoints/admin/unverify-user.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User from '../../../../models/user'; +import { Users } from '../../../../models'; export const meta = { desc: { @@ -17,7 +17,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to unverify' @@ -27,21 +26,13 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); } - await User.findOneAndUpdate({ - _id: user._id - }, { - $set: { - isVerified: false - } + await Users.update(user.id, { + isVerified: false }); - - return; }); diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts index f8f7cb5d9a..e242ac71a1 100644 --- a/src/server/api/endpoints/admin/update-meta.ts +++ b/src/server/api/endpoints/admin/update-meta.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; -import Meta from '../../../../models/meta'; import define from '../../define'; +import { Metas } from '../../../../models'; +import { Meta } from '../../../../models/entities/meta'; export const meta = { desc: { @@ -55,7 +56,7 @@ export const meta = { } }, - hidedTags: { + hiddenTags: { validator: $.optional.nullable.arr($.str), desc: { 'ja-JP': '統計などで無視するハッシュタグ' @@ -253,27 +254,6 @@ export const meta = { } }, - enableExternalUserRecommendation: { - validator: $.optional.bool, - desc: { - 'ja-JP': '外部ユーザーレコメンデーションを有効にする' - } - }, - - externalUserRecommendationEngine: { - validator: $.optional.nullable.str, - desc: { - 'ja-JP': '外部ユーザーレコメンデーションのサードパーティエンジン' - } - }, - - externalUserRecommendationTimeout: { - validator: $.optional.nullable.num.min(0), - desc: { - 'ja-JP': '外部ユーザーレコメンデーションのタイムアウト (ミリ秒)' - } - }, - enableEmail: { validator: $.optional.bool, desc: { @@ -347,7 +327,7 @@ export const meta = { }; export default define(meta, async (ps) => { - const set = {} as any; + const set = {} as Partial<Meta>; if (ps.announcements) { set.announcements = ps.announcements; @@ -373,8 +353,8 @@ export default define(meta, async (ps) => { set.useStarForReactionFallback = ps.useStarForReactionFallback; } - if (Array.isArray(ps.hidedTags)) { - set.hidedTags = ps.hidedTags; + if (Array.isArray(ps.hiddenTags)) { + set.hiddenTags = ps.hiddenTags; } if (ps.mascotImageUrl !== undefined) { @@ -430,11 +410,11 @@ export default define(meta, async (ps) => { } if (ps.maintainerName !== undefined) { - set['maintainer.name'] = ps.maintainerName; + set.maintainerName = ps.maintainerName; } if (ps.maintainerEmail !== undefined) { - set['maintainer.email'] = ps.maintainerEmail; + set.maintainerEmail = ps.maintainerEmail; } if (ps.langs !== undefined) { @@ -481,18 +461,6 @@ export default define(meta, async (ps) => { set.discordClientSecret = ps.discordClientSecret; } - if (ps.enableExternalUserRecommendation !== undefined) { - set.enableExternalUserRecommendation = ps.enableExternalUserRecommendation; - } - - if (ps.externalUserRecommendationEngine !== undefined) { - set.externalUserRecommendationEngine = ps.externalUserRecommendationEngine; - } - - if (ps.externalUserRecommendationTimeout !== undefined) { - set.externalUserRecommendationTimeout = ps.externalUserRecommendationTimeout; - } - if (ps.enableEmail !== undefined) { set.enableEmail = ps.enableEmail; } @@ -537,9 +505,11 @@ export default define(meta, async (ps) => { set.swPrivateKey = ps.swPrivateKey; } - await Meta.update({}, { - $set: set - }, { upsert: true }); + const meta = await Metas.findOne(); - return; + if (meta) { + await Metas.update(meta.id, set); + } else { + await Metas.save(set); + } }); diff --git a/src/server/api/endpoints/admin/update-remote-user.ts b/src/server/api/endpoints/admin/update-remote-user.ts index a74685912c..0be9047d5a 100644 --- a/src/server/api/endpoints/admin/update-remote-user.ts +++ b/src/server/api/endpoints/admin/update-remote-user.ts @@ -1,6 +1,5 @@ -import * as mongo from 'mongodb'; import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { getRemoteUser } from '../../common/getters'; import { updatePerson } from '../../../../remote/activitypub/models/person'; @@ -19,7 +18,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to update' @@ -29,11 +27,6 @@ export const meta = { }; export default define(meta, async (ps) => { - await updatePersonById(ps.userId); - return; -}); - -async function updatePersonById(userId: mongo.ObjectID) { - const user = await getRemoteUser(userId); + const user = await getRemoteUser(ps.userId); await updatePerson(user.uri); -} +}); diff --git a/src/server/api/endpoints/admin/verify-user.ts b/src/server/api/endpoints/admin/verify-user.ts index f67b6c3bf0..c1b447a92b 100644 --- a/src/server/api/endpoints/admin/verify-user.ts +++ b/src/server/api/endpoints/admin/verify-user.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User from '../../../../models/user'; +import { Users } from '../../../../models'; export const meta = { desc: { @@ -17,7 +17,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーID', 'en-US': 'The user ID which you want to verify' @@ -27,21 +26,13 @@ export const meta = { }; export default define(meta, async (ps) => { - const user = await User.findOne({ - _id: ps.userId - }); + const user = await Users.findOne(ps.userId as string); if (user == null) { throw new Error('user not found'); } - await User.findOneAndUpdate({ - _id: user._id - }, { - $set: { - isVerified: true - } + await Users.update(user.id, { + isVerified: true }); - - return; }); diff --git a/src/server/api/endpoints/aggregation/hashtags.ts b/src/server/api/endpoints/aggregation/hashtags.ts deleted file mode 100644 index 978e9f64b7..0000000000 --- a/src/server/api/endpoints/aggregation/hashtags.ts +++ /dev/null @@ -1,72 +0,0 @@ -import Note from '../../../../models/note'; -import define from '../../define'; -import fetchMeta from '../../../../misc/fetch-meta'; - -export const meta = { - tags: ['hashtags'], - - requireCredential: false, -}; - -export default define(meta, async (ps) => { - const instance = await fetchMeta(); - const hidedTags = instance.hidedTags.map(t => t.toLowerCase()); - - // 重い - //const span = 1000 * 60 * 60 * 24 * 7; // 1週間 - const span = 1000 * 60 * 60 * 24; // 1日 - - //#region 1. 指定期間の内に投稿されたハッシュタグ(とユーザーのペア)を集計 - const data = await Note.aggregate([{ - $match: { - createdAt: { - $gt: new Date(Date.now() - span) - }, - tagsLower: { - $exists: true, - $ne: [] - } - } - }, { - $unwind: '$tagsLower' - }, { - $group: { - _id: { tag: '$tagsLower', userId: '$userId' } - } - }]) as { - _id: { - tag: string; - userId: any; - } - }[]; - //#endregion - - if (data.length == 0) { - return []; - } - - let tags: { - name: string; - count: number; - }[] = []; - - // カウント - for (const x of data.map(x => x._id).filter(x => !hidedTags.includes(x.tag))) { - const i = tags.findIndex(tag => tag.name == x.tag); - if (i != -1) { - tags[i].count++; - } else { - tags.push({ - name: x.tag, - count: 1 - }); - } - } - - // タグを人気順に並べ替え - tags.sort((a, b) => b.count - a.count); - - tags = tags.slice(0, 30); - - return tags; -}); diff --git a/src/server/api/endpoints/ap/show.ts b/src/server/api/endpoints/ap/show.ts index 7f4afa1f6e..5b2aaeadbb 100644 --- a/src/server/api/endpoints/ap/show.ts +++ b/src/server/api/endpoints/ap/show.ts @@ -1,15 +1,15 @@ import $ from 'cafy'; import define from '../../define'; import config from '../../../../config'; -import * as mongo from 'mongodb'; -import User, { pack as packUser, IUser } from '../../../../models/user'; import { createPerson } from '../../../../remote/activitypub/models/person'; -import Note, { pack as packNote, INote } from '../../../../models/note'; import { createNote } from '../../../../remote/activitypub/models/note'; import Resolver from '../../../../remote/activitypub/resolver'; import { ApiError } from '../../error'; -import Instance from '../../../../models/instance'; import { extractDbHost } from '../../../../misc/convert-host'; +import { Users, Notes } from '../../../../models'; +import { Note } from '../../../../models/entities/note'; +import { User } from '../../../../models/entities/user'; +import fetchMeta from '../../../../misc/fetch-meta'; export const meta = { tags: ['federation'], @@ -53,25 +53,40 @@ export default define(meta, async (ps) => { async function fetchAny(uri: string) { // URIがこのサーバーを指しているなら、ローカルユーザーIDとしてDBからフェッチ if (uri.startsWith(config.url + '/')) { - const id = new mongo.ObjectID(uri.split('/').pop()); - const [user, note] = await Promise.all([ - User.findOne({ _id: id }), - Note.findOne({ _id: id }) - ]); + const parts = uri.split('/'); + const id = parts.pop(); + const type = parts.pop(); - const packed = await mergePack(user, note); - if (packed !== null) return packed; + if (type === 'notes') { + const note = await Notes.findOne(id); + + if (note) { + return { + type: 'Note', + object: await Notes.pack(note, null, { detail: true }) + }; + } + } else if (type === 'users') { + const user = await Users.findOne(id); + + if (user) { + return { + type: 'User', + object: await Users.pack(user, null, { detail: true }) + }; + } + } } // ブロックしてたら中断 - const instance = await Instance.findOne({ host: extractDbHost(uri) }); - if (instance && instance.isBlocked) return null; + const meta = await fetchMeta(); + if (meta.blockedHosts.includes(extractDbHost(uri))) return null; // URI(AP Object id)としてDB検索 { const [user, note] = await Promise.all([ - User.findOne({ uri: uri }), - Note.findOne({ uri: uri }) + Users.findOne({ uri: uri }), + Notes.findOne({ uri: uri }) ]); const packed = await mergePack(user, note); @@ -86,8 +101,8 @@ async function fetchAny(uri: string) { // これはDBに存在する可能性があるため再度DB検索 if (uri !== object.id) { const [user, note] = await Promise.all([ - User.findOne({ uri: object.id }), - Note.findOne({ uri: object.id }) + Users.findOne({ uri: object.id }), + Notes.findOne({ uri: object.id }) ]); const packed = await mergePack(user, note); @@ -99,7 +114,7 @@ async function fetchAny(uri: string) { const user = await createPerson(object.id); return { type: 'User', - object: await packUser(user, null, { detail: true }) + object: await Users.pack(user, null, { detail: true }) }; } @@ -107,25 +122,25 @@ async function fetchAny(uri: string) { const note = await createNote(object.id); return { type: 'Note', - object: await packNote(note, null, { detail: true }) + object: await Notes.pack(note, null, { detail: true }) }; } return null; } -async function mergePack(user: IUser, note: INote) { +async function mergePack(user: User, note: Note) { if (user !== null) { return { type: 'User', - object: await packUser(user, null, { detail: true }) + object: await Users.pack(user, null, { detail: true }) }; } if (note !== null) { return { type: 'Note', - object: await packNote(note, null, { detail: true }) + object: await Notes.pack(note, null, { detail: true }) }; } diff --git a/src/server/api/endpoints/app/create.ts b/src/server/api/endpoints/app/create.ts index 67b1b8150a..c7e7e516ad 100644 --- a/src/server/api/endpoints/app/create.ts +++ b/src/server/api/endpoints/app/create.ts @@ -1,7 +1,8 @@ import rndstr from 'rndstr'; import $ from 'cafy'; -import App, { pack } from '../../../../models/app'; import define from '../../define'; +import { Apps } from '../../../../models'; +import { genId } from '../../../../misc/gen-id'; export const meta = { tags: ['app'], @@ -34,9 +35,10 @@ export default define(meta, async (ps, user) => { const secret = rndstr('a-zA-Z0-9', 32); // Create account - const app = await App.insert({ + const app = await Apps.save({ + id: genId(), createdAt: new Date(), - userId: user && user._id, + userId: user && user.id, name: ps.name, description: ps.description, permission: ps.permission, @@ -44,7 +46,7 @@ export default define(meta, async (ps, user) => { secret: secret }); - return await pack(app, null, { + return await Apps.pack(app, null, { detail: true, includeSecret: true }); diff --git a/src/server/api/endpoints/app/show.ts b/src/server/api/endpoints/app/show.ts index f3f5b843b3..ce9baed2ae 100644 --- a/src/server/api/endpoints/app/show.ts +++ b/src/server/api/endpoints/app/show.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import App, { pack } from '../../../../models/app'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { ApiError } from '../../error'; +import { Apps } from '../../../../models'; export const meta = { tags: ['app'], @@ -10,7 +10,6 @@ export const meta = { params: { appId: { validator: $.type(ID), - transform: transform }, }, @@ -27,14 +26,14 @@ export default define(meta, async (ps, user, app) => { const isSecure = user != null && app == null; // Lookup app - const ap = await App.findOne({ _id: ps.appId }); + const ap = await Apps.findOne(ps.appId); - if (ap === null) { + if (ap == null) { throw new ApiError(meta.errors.noSuchApp); } - return await pack(ap, user, { + return await Apps.pack(ap, user, { detail: true, - includeSecret: isSecure && ap.userId.equals(user._id) + includeSecret: isSecure && (ap.userId === user.id) }); }); diff --git a/src/server/api/endpoints/auth/accept.ts b/src/server/api/endpoints/auth/accept.ts index cedf7821fe..21a78011dc 100644 --- a/src/server/api/endpoints/auth/accept.ts +++ b/src/server/api/endpoints/auth/accept.ts @@ -1,11 +1,10 @@ import rndstr from 'rndstr'; import * as crypto from 'crypto'; import $ from 'cafy'; -import App from '../../../../models/app'; -import AuthSess from '../../../../models/auth-session'; -import AccessToken from '../../../../models/access-token'; import define from '../../define'; import { ApiError } from '../../error'; +import { AuthSessions, AccessTokens, Apps } from '../../../../models'; +import { genId } from '../../../../misc/gen-id'; export const meta = { tags: ['auth'], @@ -31,10 +30,10 @@ export const meta = { export default define(meta, async (ps, user) => { // Fetch token - const session = await AuthSess + const session = await AuthSessions .findOne({ token: ps.token }); - if (session === null) { + if (session == null) { throw new ApiError(meta.errors.noSuchSession); } @@ -42,16 +41,14 @@ export default define(meta, async (ps, user) => { const accessToken = rndstr('a-zA-Z0-9', 32); // Fetch exist access token - const exist = await AccessToken.findOne({ + const exist = await AccessTokens.findOne({ appId: session.appId, - userId: user._id, + userId: user.id, }); - if (exist === null) { + if (exist == null) { // Lookup app - const app = await App.findOne({ - _id: session.appId - }); + const app = await Apps.findOne(session.appId); // Generate Hash const sha256 = crypto.createHash('sha256'); @@ -59,20 +56,19 @@ export default define(meta, async (ps, user) => { const hash = sha256.digest('hex'); // Insert access token doc - await AccessToken.insert({ + await AccessTokens.save({ + id: genId(), createdAt: new Date(), appId: session.appId, - userId: user._id, + userId: user.id, token: accessToken, hash: hash }); } // Update session - await AuthSess.update(session._id, { - $set: { - userId: user._id - } + await AuthSessions.update(session.id, { + userId: user.id }); return; diff --git a/src/server/api/endpoints/auth/session/generate.ts b/src/server/api/endpoints/auth/session/generate.ts index e12bea7681..5a9bfe6451 100644 --- a/src/server/api/endpoints/auth/session/generate.ts +++ b/src/server/api/endpoints/auth/session/generate.ts @@ -1,10 +1,10 @@ import * as uuid from 'uuid'; import $ from 'cafy'; -import App from '../../../../../models/app'; -import AuthSess from '../../../../../models/auth-session'; import config from '../../../../../config'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { Apps, AuthSessions } from '../../../../../models'; +import { genId } from '../../../../../misc/gen-id'; export const meta = { tags: ['auth'], @@ -46,7 +46,7 @@ export const meta = { export default define(meta, async (ps) => { // Lookup app - const app = await App.findOne({ + const app = await Apps.findOne({ secret: ps.appSecret }); @@ -58,9 +58,10 @@ export default define(meta, async (ps) => { const token = uuid.v4(); // Create session token document - const doc = await AuthSess.insert({ + const doc = await AuthSessions.save({ + id: genId(), createdAt: new Date(), - appId: app._id, + appId: app.id, token: token }); diff --git a/src/server/api/endpoints/auth/session/show.ts b/src/server/api/endpoints/auth/session/show.ts index 992e0a499e..e6ecd8b839 100644 --- a/src/server/api/endpoints/auth/session/show.ts +++ b/src/server/api/endpoints/auth/session/show.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import AuthSess, { pack } from '../../../../../models/auth-session'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { AuthSessions } from '../../../../../models'; export const meta = { tags: ['auth'], @@ -29,7 +29,7 @@ export const meta = { export default define(meta, async (ps, user) => { // Lookup session - const session = await AuthSess.findOne({ + const session = await AuthSessions.findOne({ token: ps.token }); @@ -37,5 +37,5 @@ export default define(meta, async (ps, user) => { throw new ApiError(meta.errors.noSuchSession); } - return await pack(session, user); + return await AuthSessions.pack(session, user); }); diff --git a/src/server/api/endpoints/auth/session/userkey.ts b/src/server/api/endpoints/auth/session/userkey.ts index e09e16e658..8524b96f94 100644 --- a/src/server/api/endpoints/auth/session/userkey.ts +++ b/src/server/api/endpoints/auth/session/userkey.ts @@ -1,10 +1,7 @@ import $ from 'cafy'; -import App from '../../../../../models/app'; -import AuthSess from '../../../../../models/auth-session'; -import AccessToken from '../../../../../models/access-token'; -import { pack } from '../../../../../models/user'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { Apps, AuthSessions, AccessTokens, Users } from '../../../../../models'; export const meta = { tags: ['auth'], @@ -67,7 +64,7 @@ export const meta = { export default define(meta, async (ps) => { // Lookup app - const app = await App.findOne({ + const app = await Apps.findOne({ secret: ps.appSecret }); @@ -76,13 +73,12 @@ export default define(meta, async (ps) => { } // Fetch token - const session = await AuthSess - .findOne({ - token: ps.token, - appId: app._id - }); + const session = await AuthSessions.findOne({ + token: ps.token, + appId: app.id + }); - if (session === null) { + if (session == null) { throw new ApiError(meta.errors.noSuchSession); } @@ -91,25 +87,17 @@ export default define(meta, async (ps) => { } // Lookup access token - const accessToken = await AccessToken.findOne({ - appId: app._id, + const accessToken = await AccessTokens.findOne({ + appId: app.id, userId: session.userId }); // Delete session - - /* https://github.com/Automattic/monk/issues/178 - AuthSess.deleteOne({ - _id: session._id - }); - */ - AuthSess.remove({ - _id: session._id - }); + AuthSessions.delete(session.id); return { accessToken: accessToken.token, - user: await pack(session.userId, null, { + user: await Users.pack(session.userId, null, { detail: true }) }; diff --git a/src/server/api/endpoints/blocking/create.ts b/src/server/api/endpoints/blocking/create.ts index e723cb0386..0d6626b2d5 100644 --- a/src/server/api/endpoints/blocking/create.ts +++ b/src/server/api/endpoints/blocking/create.ts @@ -1,12 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import * as ms from 'ms'; -import { pack } from '../../../../models/user'; -import Blocking from '../../../../models/blocking'; import create from '../../../../services/blocking/create'; import define from '../../define'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { Blockings, NoteWatchings } from '../../../../models'; export const meta = { stability: 'stable', @@ -25,12 +24,11 @@ export const meta = { requireCredential: true, - kind: 'following-write', + kind: 'write:blocks', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -63,7 +61,7 @@ export default define(meta, async (ps, user) => { const blocker = user; // 自分自身 - if (user._id.equals(ps.userId)) { + if (user.id === ps.userId) { throw new ApiError(meta.errors.blockeeIsYourself); } @@ -74,19 +72,22 @@ export default define(meta, async (ps, user) => { }); // Check if already blocking - const exist = await Blocking.findOne({ - blockerId: blocker._id, - blockeeId: blockee._id + const exist = await Blockings.findOne({ + blockerId: blocker.id, + blockeeId: blockee.id }); - if (exist !== null) { + if (exist != null) { throw new ApiError(meta.errors.alreadyBlocking); } // Create blocking await create(blocker, blockee); - return await pack(blockee._id, user, { - detail: true + NoteWatchings.delete({ + userId: blocker.id, + noteUserId: blockee.id }); + + return await Blockings.pack(blockee.id, user); }); diff --git a/src/server/api/endpoints/blocking/delete.ts b/src/server/api/endpoints/blocking/delete.ts index 2a9fdc5e24..e304dca811 100644 --- a/src/server/api/endpoints/blocking/delete.ts +++ b/src/server/api/endpoints/blocking/delete.ts @@ -1,12 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import * as ms from 'ms'; -import { pack } from '../../../../models/user'; -import Blocking from '../../../../models/blocking'; import deleteBlocking from '../../../../services/blocking/delete'; import define from '../../define'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { Blockings } from '../../../../models'; export const meta = { stability: 'stable', @@ -25,12 +24,11 @@ export const meta = { requireCredential: true, - kind: 'following-write', + kind: 'write:blocks', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -63,7 +61,7 @@ export default define(meta, async (ps, user) => { const blocker = user; // Check if the blockee is yourself - if (user._id.equals(ps.userId)) { + if (user.id === ps.userId) { throw new ApiError(meta.errors.blockeeIsYourself); } @@ -74,19 +72,17 @@ export default define(meta, async (ps, user) => { }); // Check not blocking - const exist = await Blocking.findOne({ - blockerId: blocker._id, - blockeeId: blockee._id + const exist = await Blockings.findOne({ + blockerId: blocker.id, + blockeeId: blockee.id }); - if (exist === null) { + if (exist == null) { throw new ApiError(meta.errors.notBlocking); } // Delete blocking await deleteBlocking(blocker, blockee); - return await pack(blockee._id, user, { - detail: true - }); + return await Blockings.pack(blockee.id, user); }); diff --git a/src/server/api/endpoints/blocking/list.ts b/src/server/api/endpoints/blocking/list.ts index b9ad6e8a3f..a078891ab0 100644 --- a/src/server/api/endpoints/blocking/list.ts +++ b/src/server/api/endpoints/blocking/list.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Blocking, { packMany } from '../../../../models/blocking'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; +import { Blockings } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { desc: { @@ -13,7 +14,7 @@ export const meta = { requireCredential: true, - kind: 'following-read', + kind: 'read:blocks', params: { limit: { @@ -23,12 +24,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, }, @@ -41,30 +40,12 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const query = { - blockerId: me._id - } as any; + const query = makePaginationQuery(Blockings.createQueryBuilder('blocking'), ps.sinceId, ps.untilId) + .andWhere(`blocking.blockerId = :meId`, { meId: me.id }); - const sort = { - _id: -1 - }; + const blockings = await query + .take(ps.limit) + .getMany(); - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } - - const blockings = await Blocking - .find(query, { - limit: ps.limit, - sort: sort - }); - - return await packMany(blockings, me); + return await Blockings.packMany(blockings, me); }); diff --git a/src/server/api/endpoints/charts/active-users.ts b/src/server/api/endpoints/charts/active-users.ts index 9dad942e06..60fa72c5c7 100644 --- a/src/server/api/endpoints/charts/active-users.ts +++ b/src/server/api/endpoints/charts/active-users.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import activeUsersChart from '../../../../services/chart/active-users'; +import { convertLog } from '../../../../services/chart/core'; +import { activeUsersChart } from '../../../../services/chart'; export const meta = { stability: 'stable', @@ -28,12 +29,7 @@ export const meta = { }, }, - res: { - type: 'array', - items: { - type: 'object', - }, - }, + res: convertLog(activeUsersChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/drive.ts b/src/server/api/endpoints/charts/drive.ts index 6bbb266f96..a9676e1586 100644 --- a/src/server/api/endpoints/charts/drive.ts +++ b/src/server/api/endpoints/charts/drive.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import driveChart, { driveLogSchema } from '../../../../services/chart/drive'; -import { convertLog } from '../../../../services/chart'; +import { convertLog } from '../../../../services/chart/core'; +import { driveChart } from '../../../../services/chart'; export const meta = { stability: 'stable', @@ -29,7 +29,7 @@ export const meta = { }, }, - res: convertLog(driveLogSchema), + res: convertLog(driveChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/federation.ts b/src/server/api/endpoints/charts/federation.ts index c7b34f1015..95f16c76a7 100644 --- a/src/server/api/endpoints/charts/federation.ts +++ b/src/server/api/endpoints/charts/federation.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import federationChart from '../../../../services/chart/federation'; +import { convertLog } from '../../../../services/chart/core'; +import { federationChart } from '../../../../services/chart'; export const meta = { stability: 'stable', @@ -28,12 +29,7 @@ export const meta = { }, }, - res: { - type: 'array', - items: { - type: 'object', - }, - }, + res: convertLog(federationChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/hashtag.ts b/src/server/api/endpoints/charts/hashtag.ts index 4db6e62408..a7ec12707e 100644 --- a/src/server/api/endpoints/charts/hashtag.ts +++ b/src/server/api/endpoints/charts/hashtag.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import hashtagChart from '../../../../services/chart/hashtag'; +import { convertLog } from '../../../../services/chart/core'; +import { hashtagChart } from '../../../../services/chart'; export const meta = { stability: 'stable', @@ -35,12 +36,7 @@ export const meta = { }, }, - res: { - type: 'array', - items: { - type: 'object', - }, - }, + res: convertLog(hashtagChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/instance.ts b/src/server/api/endpoints/charts/instance.ts index 3fe85f086a..cf3094f7e1 100644 --- a/src/server/api/endpoints/charts/instance.ts +++ b/src/server/api/endpoints/charts/instance.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import instanceChart from '../../../../services/chart/instance'; +import { convertLog } from '../../../../services/chart/core'; +import { instanceChart } from '../../../../services/chart'; export const meta = { stability: 'stable', @@ -36,12 +37,7 @@ export const meta = { } }, - res: { - type: 'array', - items: { - type: 'object', - }, - }, + res: convertLog(instanceChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/network.ts b/src/server/api/endpoints/charts/network.ts index 48b1d0f66f..c0fcd95fe9 100644 --- a/src/server/api/endpoints/charts/network.ts +++ b/src/server/api/endpoints/charts/network.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import networkChart from '../../../../services/chart/network'; +import { convertLog } from '../../../../services/chart/core'; +import { networkChart } from '../../../../services/chart'; export const meta = { stability: 'stable', @@ -28,12 +29,7 @@ export const meta = { }, }, - res: { - type: 'array', - items: { - type: 'object', - }, - }, + res: convertLog(networkChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/notes.ts b/src/server/api/endpoints/charts/notes.ts index cc0ca8bef7..86f30e4b89 100644 --- a/src/server/api/endpoints/charts/notes.ts +++ b/src/server/api/endpoints/charts/notes.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import notesChart, { notesLogSchema } from '../../../../services/chart/notes'; -import { convertLog } from '../../../../services/chart'; +import { convertLog } from '../../../../services/chart/core'; +import { notesChart } from '../../../../services/chart'; export const meta = { stability: 'stable', @@ -29,7 +29,7 @@ export const meta = { }, }, - res: convertLog(notesLogSchema), + res: convertLog(notesChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/user/drive.ts b/src/server/api/endpoints/charts/user/drive.ts index 064c7c7b72..e3696dfda1 100644 --- a/src/server/api/endpoints/charts/user/drive.ts +++ b/src/server/api/endpoints/charts/user/drive.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; import define from '../../../define'; -import perUserDriveChart, { perUserDriveLogSchema } from '../../../../../services/chart/per-user-drive'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import { convertLog } from '../../../../../services/chart'; +import { ID } from '../../../../../misc/cafy-id'; +import { convertLog } from '../../../../../services/chart/core'; +import { perUserDriveChart } from '../../../../../services/chart'; export const meta = { stability: 'stable', @@ -31,7 +31,6 @@ export const meta = { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -39,7 +38,7 @@ export const meta = { } }, - res: convertLog(perUserDriveLogSchema), + res: convertLog(perUserDriveChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/user/following.ts b/src/server/api/endpoints/charts/user/following.ts index f5b1355038..8feba0bd16 100644 --- a/src/server/api/endpoints/charts/user/following.ts +++ b/src/server/api/endpoints/charts/user/following.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; import define from '../../../define'; -import perUserFollowingChart, { perUserFollowingLogSchema } from '../../../../../services/chart/per-user-following'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import { convertLog } from '../../../../../services/chart'; +import { ID } from '../../../../../misc/cafy-id'; +import { convertLog } from '../../../../../services/chart/core'; +import { perUserFollowingChart } from '../../../../../services/chart'; export const meta = { stability: 'stable', @@ -31,7 +31,6 @@ export const meta = { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -39,9 +38,9 @@ export const meta = { } }, - res: convertLog(perUserFollowingLogSchema), + res: convertLog(perUserFollowingChart.schema), }; export default define(meta, async (ps) => { - return await perUserFollowingChart.getChart(ps.span as any, ps.limit, ps.userId); + return await perUserFollowingChart.getChart(ps.span as any, ps.limit, ps.userId); }); diff --git a/src/server/api/endpoints/charts/user/notes.ts b/src/server/api/endpoints/charts/user/notes.ts index 7e31978bf3..8c1db54f76 100644 --- a/src/server/api/endpoints/charts/user/notes.ts +++ b/src/server/api/endpoints/charts/user/notes.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; import define from '../../../define'; -import perUserNotesChart, { perUserNotesLogSchema } from '../../../../../services/chart/per-user-notes'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import { convertLog } from '../../../../../services/chart'; +import { ID } from '../../../../../misc/cafy-id'; +import { convertLog } from '../../../../../services/chart/core'; +import { perUserNotesChart } from '../../../../../services/chart'; export const meta = { stability: 'stable', @@ -31,7 +31,6 @@ export const meta = { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -39,7 +38,7 @@ export const meta = { } }, - res: convertLog(perUserNotesLogSchema), + res: convertLog(perUserNotesChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/user/reactions.ts b/src/server/api/endpoints/charts/user/reactions.ts index 51ff83f20e..7c9b2508ae 100644 --- a/src/server/api/endpoints/charts/user/reactions.ts +++ b/src/server/api/endpoints/charts/user/reactions.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; import define from '../../../define'; -import perUserReactionsChart from '../../../../../services/chart/per-user-reactions'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; +import { convertLog } from '../../../../../services/chart/core'; +import { perUserReactionsChart } from '../../../../../services/chart'; export const meta = { stability: 'stable', @@ -30,7 +31,6 @@ export const meta = { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -38,12 +38,7 @@ export const meta = { } }, - res: { - type: 'array', - items: { - type: 'object', - }, - }, + res: convertLog(perUserReactionsChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/charts/users.ts b/src/server/api/endpoints/charts/users.ts index 9de54a630e..3ed5e09349 100644 --- a/src/server/api/endpoints/charts/users.ts +++ b/src/server/api/endpoints/charts/users.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import usersChart, { usersLogSchema } from '../../../../services/chart/users'; -import { convertLog } from '../../../../services/chart'; +import { convertLog } from '../../../../services/chart/core'; +import { usersChart } from '../../../../services/chart'; export const meta = { stability: 'stable', @@ -29,7 +29,7 @@ export const meta = { }, }, - res: convertLog(usersLogSchema), + res: convertLog(usersChart.schema), }; export default define(meta, async (ps) => { diff --git a/src/server/api/endpoints/drive.ts b/src/server/api/endpoints/drive.ts index 138adffad2..adf780301b 100644 --- a/src/server/api/endpoints/drive.ts +++ b/src/server/api/endpoints/drive.ts @@ -1,6 +1,6 @@ -import DriveFile from '../../../models/drive-file'; import define from '../define'; import fetchMeta from '../../../misc/fetch-meta'; +import { DriveFiles } from '../../../models'; export const meta = { desc: { @@ -12,7 +12,7 @@ export const meta = { requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', res: { type: 'object', @@ -31,27 +31,7 @@ export default define(meta, async (ps, user) => { const instance = await fetchMeta(); // Calculate drive usage - const usage = await DriveFile.aggregate([{ - $match: { - 'metadata.userId': user._id, - 'metadata.deletedAt': { $exists: false } - } - }, { - $project: { - length: true - } - }, { - $group: { - _id: null, - usage: { $sum: '$length' } - } - }]) - .then((aggregates: any[]) => { - if (aggregates.length > 0) { - return aggregates[0].usage; - } - return 0; - }); + const usage = await DriveFiles.clacDriveUsageOf(user); return { capacity: 1024 * 1024 * instance.localDriveCapacityMb, diff --git a/src/server/api/endpoints/drive/files.ts b/src/server/api/endpoints/drive/files.ts index f108e820e7..400b73d3b7 100644 --- a/src/server/api/endpoints/drive/files.ts +++ b/src/server/api/endpoints/drive/files.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import DriveFile, { packMany } from '../../../../models/drive-file'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; +import { DriveFiles } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { desc: { @@ -13,7 +14,7 @@ export const meta = { requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', params: { limit: { @@ -23,18 +24,15 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, folderId: { validator: $.optional.nullable.type(ID), default: null as any, - transform: transform, }, type: { @@ -51,36 +49,24 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const sort = { - _id: -1 - }; + const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId) + .andWhere('file.userId = :userId', { userId: user.id }); - const query = { - 'metadata.userId': user._id, - 'metadata.folderId': ps.folderId, - 'metadata.deletedAt': { $exists: false } - } as any; - - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; + if (ps.folderId) { + query.andWhere('file.folderId = :folderId', { folderId: ps.folderId }); + } else { + query.andWhere('file.folderId IS NULL'); } if (ps.type) { - query.contentType = new RegExp(`^${ps.type.replace(/\*/g, '.+?')}$`); + if (ps.type.endsWith('/*')) { + query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); + } else { + query.andWhere('file.type = :type', { type: ps.type }); + } } - const files = await DriveFile - .find(query, { - limit: ps.limit, - sort: sort - }); + const files = await query.take(ps.limit).getMany(); - return await packMany(files, { detail: false, self: true }); + return await DriveFiles.packMany(files, { detail: false, self: true }); }); diff --git a/src/server/api/endpoints/drive/files/attached-notes.ts b/src/server/api/endpoints/drive/files/attached-notes.ts index c9eeab58c5..7214463dde 100644 --- a/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/src/server/api/endpoints/drive/files/attached-notes.ts @@ -1,9 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFile from '../../../../../models/drive-file'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; -import { packMany } from '../../../../../models/note'; import { ApiError } from '../../../error'; +import { DriveFiles } from '../../../../../models'; export const meta = { stability: 'stable', @@ -17,12 +16,11 @@ export const meta = { requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', params: { fileId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のファイルID', 'en-US': 'Target file ID' @@ -48,18 +46,17 @@ export const meta = { export default define(meta, async (ps, user) => { // Fetch file - const file = await DriveFile - .findOne({ - _id: ps.fileId, - 'metadata.userId': user._id, - 'metadata.deletedAt': { $exists: false } - }); + const file = await DriveFiles.findOne({ + id: ps.fileId, + userId: user.id, + }); - if (file === null) { + if (file == null) { throw new ApiError(meta.errors.noSuchFile); } + /* v11 TODO return await packMany(file.metadata.attachedNoteIds || [], user, { detail: true - }); + });*/ }); diff --git a/src/server/api/endpoints/drive/files/check-existence.ts b/src/server/api/endpoints/drive/files/check-existence.ts index 926411c83a..3a87a9497f 100644 --- a/src/server/api/endpoints/drive/files/check-existence.ts +++ b/src/server/api/endpoints/drive/files/check-existence.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; -import DriveFile, { pack } from '../../../../../models/drive-file'; import define from '../../../define'; +import { DriveFiles } from '../../../../../models'; export const meta = { desc: { @@ -12,7 +12,7 @@ export const meta = { requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', params: { md5: { @@ -29,11 +29,12 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const file = await DriveFile.findOne({ + const file = await DriveFiles.findOne({ md5: ps.md5, - 'metadata.userId': user._id, - 'metadata.deletedAt': { $exists: false } + userId: user.id, }); - return { file: file ? await pack(file, { self: true }) : null }; + return { + file: file ? await DriveFiles.pack(file, { self: true }) : null + }; }); diff --git a/src/server/api/endpoints/drive/files/create.ts b/src/server/api/endpoints/drive/files/create.ts index b2979c4888..5702c70fc0 100644 --- a/src/server/api/endpoints/drive/files/create.ts +++ b/src/server/api/endpoints/drive/files/create.ts @@ -1,11 +1,11 @@ import * as ms from 'ms'; import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import { validateFileName, pack } from '../../../../../models/drive-file'; +import { ID } from '../../../../../misc/cafy-id'; import create from '../../../../../services/drive/add-file'; import define from '../../../define'; import { apiLogger } from '../../../logger'; import { ApiError } from '../../../error'; +import { DriveFiles } from '../../../../../models'; export const meta = { desc: { @@ -24,12 +24,11 @@ export const meta = { requireFile: true, - kind: 'drive-write', + kind: 'write:drive', params: { folderId: { validator: $.optional.nullable.type(ID), - transform: transform, default: null as any, desc: { 'ja-JP': 'フォルダID' @@ -78,7 +77,7 @@ export default define(meta, async (ps, user, app, file, cleanup) => { name = null; } else if (name === 'blob') { name = null; - } else if (!validateFileName(name)) { + } else if (!DriveFiles.validateFileName(name)) { throw new ApiError(meta.errors.invalidFileName); } } else { @@ -88,7 +87,7 @@ export default define(meta, async (ps, user, app, file, cleanup) => { try { // Create file const driveFile = await create(user, file.path, name, null, ps.folderId, ps.force, false, null, null, ps.isSensitive); - return pack(driveFile, { self: true }); + return DriveFiles.pack(driveFile, { self: true }); } catch (e) { apiLogger.error(e); throw new ApiError(); diff --git a/src/server/api/endpoints/drive/files/delete.ts b/src/server/api/endpoints/drive/files/delete.ts index dd4e187fcd..d8cc5ec0a1 100644 --- a/src/server/api/endpoints/drive/files/delete.ts +++ b/src/server/api/endpoints/drive/files/delete.ts @@ -1,10 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFile from '../../../../../models/drive-file'; +import { ID } from '../../../../../misc/cafy-id'; import del from '../../../../../services/drive/delete-file'; import { publishDriveStream } from '../../../../../services/stream'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { DriveFiles } from '../../../../../models'; export const meta = { stability: 'stable', @@ -18,12 +18,11 @@ export const meta = { requireCredential: true, - kind: 'drive-write', + kind: 'write:drive', params: { fileId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のファイルID', 'en-US': 'Target file ID' @@ -47,17 +46,13 @@ export const meta = { }; export default define(meta, async (ps, user) => { - // Fetch file - const file = await DriveFile - .findOne({ - _id: ps.fileId - }); + const file = await DriveFiles.findOne(ps.fileId); - if (file === null) { + if (file == null) { throw new ApiError(meta.errors.noSuchFile); } - if (!user.isAdmin && !user.isModerator && !file.metadata.userId.equals(user._id)) { + if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } @@ -65,7 +60,5 @@ export default define(meta, async (ps, user) => { await del(file); // Publish fileDeleted event - publishDriveStream(user._id, 'fileDeleted', file._id); - - return; + publishDriveStream(user.id, 'fileDeleted', file.id); }); diff --git a/src/server/api/endpoints/drive/files/find.ts b/src/server/api/endpoints/drive/files/find.ts index 0d4102a48f..265850f84c 100644 --- a/src/server/api/endpoints/drive/files/find.ts +++ b/src/server/api/endpoints/drive/files/find.ts @@ -1,14 +1,14 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFile, { pack } from '../../../../../models/drive-file'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; +import { DriveFiles } from '../../../../../models'; export const meta = { requireCredential: true, tags: ['drive'], - kind: 'drive-read', + kind: 'read:drive', params: { name: { @@ -17,7 +17,6 @@ export const meta = { folderId: { validator: $.optional.nullable.type(ID), - transform: transform, default: null as any, desc: { 'ja-JP': 'フォルダID' @@ -27,12 +26,11 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const files = await DriveFile - .find({ - filename: ps.name, - 'metadata.userId': user._id, - 'metadata.folderId': ps.folderId - }); + const files = await DriveFiles.find({ + name: ps.name, + userId: user.id, + folderId: ps.folderId + }); - return await Promise.all(files.map(file => pack(file, { self: true }))); + return await Promise.all(files.map(file => DriveFiles.pack(file, { self: true }))); }); diff --git a/src/server/api/endpoints/drive/files/show.ts b/src/server/api/endpoints/drive/files/show.ts index 6d63a8605c..b516ec2df6 100644 --- a/src/server/api/endpoints/drive/files/show.ts +++ b/src/server/api/endpoints/drive/files/show.ts @@ -1,10 +1,9 @@ import $ from 'cafy'; -import * as mongo from 'mongodb'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFile, { pack, IDriveFile } from '../../../../../models/drive-file'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; -import config from '../../../../../config'; import { ApiError } from '../../../error'; +import { DriveFile } from '../../../../../models/entities/drive-file'; +import { DriveFiles } from '../../../../../models'; export const meta = { stability: 'stable', @@ -18,12 +17,11 @@ export const meta = { requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', params: { fileId: { validator: $.optional.type(ID), - transform: transform, desc: { 'ja-JP': '対象のファイルID', 'en-US': 'Target file ID' @@ -65,49 +63,33 @@ export const meta = { }; export default define(meta, async (ps, user) => { - let file: IDriveFile; + let file: DriveFile; if (ps.fileId) { - file = await DriveFile.findOne({ - _id: ps.fileId, - 'metadata.deletedAt': { $exists: false } - }); + file = await DriveFiles.findOne(ps.fileId); } else if (ps.url) { - const isInternalStorageUrl = ps.url.startsWith(config.driveUrl); - if (isInternalStorageUrl) { - // Extract file ID from url - // e.g. - // http://misskey.local/files/foo?original=bar --> foo - const fileId = new mongo.ObjectID(ps.url.replace(config.driveUrl, '').replace(/\?(.*)$/, '').replace(/\//g, '')); - file = await DriveFile.findOne({ - _id: fileId, - 'metadata.deletedAt': { $exists: false } - }); - } else { - file = await DriveFile.findOne({ - $or: [{ - 'metadata.url': ps.url - }, { - 'metadata.webpublicUrl': ps.url - }, { - 'metadata.thumbnailUrl': ps.url - }], - 'metadata.deletedAt': { $exists: false } - }); - } + file = await DriveFiles.findOne({ + where: [{ + url: ps.url + }, { + webpublicUrl: ps.url + }, { + thumbnailUrl: ps.url + }], + }); } else { throw new ApiError(meta.errors.fileIdOrUrlRequired); } - if (!user.isAdmin && !user.isModerator && !file.metadata.userId.equals(user._id)) { + if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } - if (file === null) { + if (file == null) { throw new ApiError(meta.errors.noSuchFile); } - return await pack(file, { + return await DriveFiles.pack(file, { detail: true, self: true }); diff --git a/src/server/api/endpoints/drive/files/update.ts b/src/server/api/endpoints/drive/files/update.ts index c8803bec3a..81e86a2734 100644 --- a/src/server/api/endpoints/drive/files/update.ts +++ b/src/server/api/endpoints/drive/files/update.ts @@ -1,11 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFolder from '../../../../../models/drive-folder'; -import DriveFile, { validateFileName, pack } from '../../../../../models/drive-file'; +import { ID } from '../../../../../misc/cafy-id'; import { publishDriveStream } from '../../../../../services/stream'; import define from '../../../define'; -import Note from '../../../../../models/note'; import { ApiError } from '../../../error'; +import { DriveFiles, DriveFolders } from '../../../../../models'; export const meta = { desc: { @@ -17,12 +15,11 @@ export const meta = { requireCredential: true, - kind: 'drive-write', + kind: 'write:drive', params: { fileId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のファイルID' } @@ -30,7 +27,6 @@ export const meta = { folderId: { validator: $.optional.nullable.type(ID), - transform: transform, default: undefined as any, desc: { 'ja-JP': 'フォルダID' @@ -38,7 +34,7 @@ export const meta = { }, name: { - validator: $.optional.str.pipe(validateFileName), + validator: $.optional.str.pipe(DriveFiles.validateFileName), default: undefined as any, desc: { 'ja-JP': 'ファイル名', @@ -78,69 +74,47 @@ export const meta = { }; export default define(meta, async (ps, user) => { - // Fetch file - const file = await DriveFile - .findOne({ - _id: ps.fileId - }); + const file = await DriveFiles.findOne(ps.fileId); - if (file === null) { + if (file == null) { throw new ApiError(meta.errors.noSuchFile); } - if (!user.isAdmin && !user.isModerator && !file.metadata.userId.equals(user._id)) { + if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } - if (ps.name) file.filename = ps.name; + if (ps.name) file.name = ps.name; - if (ps.isSensitive !== undefined) file.metadata.isSensitive = ps.isSensitive; + if (ps.isSensitive !== undefined) file.isSensitive = ps.isSensitive; if (ps.folderId !== undefined) { if (ps.folderId === null) { - file.metadata.folderId = null; + file.folderId = null; } else { - // Fetch folder - const folder = await DriveFolder - .findOne({ - _id: ps.folderId, - userId: user._id - }); + const folder = await DriveFolders.findOne({ + id: ps.folderId, + userId: user.id + }); - if (folder === null) { + if (folder == null) { throw new ApiError(meta.errors.noSuchFolder); } - file.metadata.folderId = folder._id; + file.folderId = folder.id; } } - await DriveFile.update(file._id, { - $set: { - filename: file.filename, - 'metadata.folderId': file.metadata.folderId, - 'metadata.isSensitive': file.metadata.isSensitive - } - }); - - // ドライブのファイルが非正規化されているドキュメントも更新 - Note.find({ - '_files._id': file._id - }).then(notes => { - for (const note of notes) { - note._files[note._files.findIndex(f => f._id.equals(file._id))] = file; - Note.update({ _id: note._id }, { - $set: { - _files: note._files - } - }); - } + await DriveFiles.update(file.id, { + name: file.name, + folderId: file.folderId, + isSensitive: file.isSensitive }); - const fileObj = await pack(file, { self: true }); + const fileObj = await DriveFiles.pack(file, { self: true }); // Publish fileUpdated event - publishDriveStream(user._id, 'fileUpdated', fileObj); + publishDriveStream(user.id, 'fileUpdated', fileObj); return fileObj; }); diff --git a/src/server/api/endpoints/drive/files/upload-from-url.ts b/src/server/api/endpoints/drive/files/upload-from-url.ts index 93a9fa62fa..034ab10f19 100644 --- a/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import * as ms from 'ms'; -import { pack } from '../../../../../models/drive-file'; import uploadFromUrl from '../../../../../services/drive/upload-from-url'; import define from '../../../define'; +import { DriveFiles } from '../../../../../models'; export const meta = { desc: { @@ -19,7 +19,7 @@ export const meta = { requireCredential: true, - kind: 'drive-write', + kind: 'write:drive', params: { url: { @@ -30,7 +30,6 @@ export const meta = { folderId: { validator: $.optional.nullable.type(ID), default: null as any, - transform: transform }, isSensitive: { @@ -53,5 +52,5 @@ export const meta = { }; export default define(meta, async (ps, user) => { - return await pack(await uploadFromUrl(ps.url, user, ps.folderId, null, ps.isSensitive, ps.force), { self: true }); + return await DriveFiles.pack(await uploadFromUrl(ps.url, user, ps.folderId, null, ps.isSensitive, ps.force), { self: true }); }); diff --git a/src/server/api/endpoints/drive/folders.ts b/src/server/api/endpoints/drive/folders.ts index 73c179f7be..f5c3816407 100644 --- a/src/server/api/endpoints/drive/folders.ts +++ b/src/server/api/endpoints/drive/folders.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import DriveFolder, { pack } from '../../../../models/drive-folder'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; +import { DriveFolders } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { desc: { @@ -13,7 +14,7 @@ export const meta = { requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', params: { limit: { @@ -23,18 +24,15 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, folderId: { validator: $.optional.nullable.type(ID), default: null as any, - transform: transform, } }, @@ -47,29 +45,16 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const sort = { - _id: -1 - }; - const query = { - userId: user._id, - parentId: ps.folderId - } as any; - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; + const query = makePaginationQuery(DriveFolders.createQueryBuilder('folder'), ps.sinceId, ps.untilId) + .andWhere('folder.userId = :userId', { userId: user.id }); + + if (ps.folderId) { + query.andWhere('folder.parentId = :parentId', { parentId: ps.folderId }); + } else { + query.andWhere('folder.parentId IS NULL'); } - const folders = await DriveFolder - .find(query, { - limit: ps.limit, - sort: sort - }); + const folders = await query.take(ps.limit).getMany(); - return await Promise.all(folders.map(folder => pack(folder))); + return await Promise.all(folders.map(folder => DriveFolders.pack(folder))); }); diff --git a/src/server/api/endpoints/drive/folders/create.ts b/src/server/api/endpoints/drive/folders/create.ts index 5fab0b91a1..5530abf9dc 100644 --- a/src/server/api/endpoints/drive/folders/create.ts +++ b/src/server/api/endpoints/drive/folders/create.ts @@ -1,9 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFolder, { isValidFolderName, pack } from '../../../../../models/drive-folder'; +import { ID } from '../../../../../misc/cafy-id'; import { publishDriveStream } from '../../../../../services/stream'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { DriveFolders } from '../../../../../models'; +import { genId } from '../../../../../misc/gen-id'; export const meta = { stability: 'stable', @@ -17,11 +18,11 @@ export const meta = { requireCredential: true, - kind: 'drive-write', + kind: 'write:drive', params: { name: { - validator: $.optional.str.pipe(isValidFolderName), + validator: $.optional.str.pipe(DriveFolders.validateFolderName), default: 'Untitled', desc: { 'ja-JP': 'フォルダ名', @@ -31,7 +32,6 @@ export const meta = { parentId: { validator: $.optional.nullable.type(ID), - transform: transform, desc: { 'ja-JP': '親フォルダID', 'en-US': 'Parent folder ID' @@ -53,29 +53,29 @@ export default define(meta, async (ps, user) => { let parent = null; if (ps.parentId) { // Fetch parent folder - parent = await DriveFolder - .findOne({ - _id: ps.parentId, - userId: user._id - }); + parent = await DriveFolders.findOne({ + id: ps.parentId, + userId: user.id + }); - if (parent === null) { + if (parent == null) { throw new ApiError(meta.errors.noSuchFolder); } } // Create folder - const folder = await DriveFolder.insert({ + const folder = await DriveFolders.save({ + id: genId(), createdAt: new Date(), name: ps.name, - parentId: parent !== null ? parent._id : null, - userId: user._id + parentId: parent !== null ? parent.id : null, + userId: user.id }); - const folderObj = await pack(folder); + const folderObj = await DriveFolders.pack(folder); // Publish folderCreated event - publishDriveStream(user._id, 'folderCreated', folderObj); + publishDriveStream(user.id, 'folderCreated', folderObj); return folderObj; }); diff --git a/src/server/api/endpoints/drive/folders/delete.ts b/src/server/api/endpoints/drive/folders/delete.ts index 9f22bf9ea7..fe6c05ad07 100644 --- a/src/server/api/endpoints/drive/folders/delete.ts +++ b/src/server/api/endpoints/drive/folders/delete.ts @@ -1,10 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFolder from '../../../../../models/drive-folder'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { publishDriveStream } from '../../../../../services/stream'; -import DriveFile from '../../../../../models/drive-file'; import { ApiError } from '../../../error'; +import { DriveFolders, DriveFiles } from '../../../../../models'; export const meta = { stability: 'stable', @@ -18,12 +17,11 @@ export const meta = { requireCredential: true, - kind: 'drive-write', + kind: 'write:drive', params: { folderId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のフォルダID', 'en-US': 'Target folder ID' @@ -48,29 +46,26 @@ export const meta = { export default define(meta, async (ps, user) => { // Get folder - const folder = await DriveFolder - .findOne({ - _id: ps.folderId, - userId: user._id - }); + const folder = await DriveFolders.findOne({ + id: ps.folderId, + userId: user.id + }); - if (folder === null) { + if (folder == null) { throw new ApiError(meta.errors.noSuchFolder); } const [childFoldersCount, childFilesCount] = await Promise.all([ - DriveFolder.count({ parentId: folder._id }), - DriveFile.count({ 'metadata.folderId': folder._id }) + DriveFolders.count({ parentId: folder.id }), + DriveFiles.count({ folderId: folder.id }) ]); if (childFoldersCount !== 0 || childFilesCount !== 0) { throw new ApiError(meta.errors.hasChildFilesOrFolders); } - await DriveFolder.remove({ _id: folder._id }); + await DriveFolders.delete(folder.id); // Publish folderCreated event - publishDriveStream(user._id, 'folderDeleted', folder._id); - - return; + publishDriveStream(user.id, 'folderDeleted', folder.id); }); diff --git a/src/server/api/endpoints/drive/folders/find.ts b/src/server/api/endpoints/drive/folders/find.ts index 16b6c10633..f0989ec5ae 100644 --- a/src/server/api/endpoints/drive/folders/find.ts +++ b/src/server/api/endpoints/drive/folders/find.ts @@ -1,14 +1,14 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFolder, { pack } from '../../../../../models/drive-folder'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; +import { DriveFolders } from '../../../../../models'; export const meta = { tags: ['drive'], requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', params: { name: { @@ -17,7 +17,6 @@ export const meta = { parentId: { validator: $.optional.nullable.type(ID), - transform: transform, default: null as any, desc: { 'ja-JP': 'フォルダID' @@ -34,12 +33,11 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const folders = await DriveFolder - .find({ - name: ps.name, - userId: user._id, - parentId: ps.parentId - }); + const folders = await DriveFolders.find({ + name: ps.name, + userId: user.id, + parentId: ps.parentId + }); - return await Promise.all(folders.map(folder => pack(folder))); + return await Promise.all(folders.map(folder => DriveFolders.pack(folder))); }); diff --git a/src/server/api/endpoints/drive/folders/show.ts b/src/server/api/endpoints/drive/folders/show.ts index bbfcbed51f..60507e7d7f 100644 --- a/src/server/api/endpoints/drive/folders/show.ts +++ b/src/server/api/endpoints/drive/folders/show.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFolder, { pack } from '../../../../../models/drive-folder'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { DriveFolders } from '../../../../../models'; export const meta = { stability: 'stable', @@ -16,12 +16,11 @@ export const meta = { requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', params: { folderId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のフォルダID', 'en-US': 'Target folder ID' @@ -44,17 +43,16 @@ export const meta = { export default define(meta, async (ps, user) => { // Get folder - const folder = await DriveFolder - .findOne({ - _id: ps.folderId, - userId: user._id - }); + const folder = await DriveFolders.findOne({ + id: ps.folderId, + userId: user.id + }); - if (folder === null) { + if (folder == null) { throw new ApiError(meta.errors.noSuchFolder); } - return await pack(folder, { + return await DriveFolders.pack(folder, { detail: true }); }); diff --git a/src/server/api/endpoints/drive/folders/update.ts b/src/server/api/endpoints/drive/folders/update.ts index a1ee2669f0..90129bed63 100644 --- a/src/server/api/endpoints/drive/folders/update.ts +++ b/src/server/api/endpoints/drive/folders/update.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import DriveFolder, { isValidFolderName, pack } from '../../../../../models/drive-folder'; +import { ID } from '../../../../../misc/cafy-id'; import { publishDriveStream } from '../../../../../services/stream'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { DriveFolders } from '../../../../../models'; export const meta = { stability: 'stable', @@ -17,12 +17,11 @@ export const meta = { requireCredential: true, - kind: 'drive-write', + kind: 'write:drive', params: { folderId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のフォルダID', 'en-US': 'Target folder ID' @@ -30,7 +29,7 @@ export const meta = { }, name: { - validator: $.optional.str.pipe(isValidFolderName), + validator: $.optional.str.pipe(DriveFolders.validateFolderName), desc: { 'ja-JP': 'フォルダ名', 'en-US': 'Folder name' @@ -39,7 +38,6 @@ export const meta = { parentId: { validator: $.optional.nullable.type(ID), - transform: transform, desc: { 'ja-JP': '親フォルダID', 'en-US': 'Parent folder ID' @@ -70,46 +68,41 @@ export const meta = { export default define(meta, async (ps, user) => { // Fetch folder - const folder = await DriveFolder - .findOne({ - _id: ps.folderId, - userId: user._id - }); + const folder = await DriveFolders.findOne({ + id: ps.folderId, + userId: user.id + }); - if (folder === null) { + if (folder == null) { throw new ApiError(meta.errors.noSuchFolder); } if (ps.name) folder.name = ps.name; if (ps.parentId !== undefined) { - if (ps.parentId.equals(folder._id)) { + if (ps.parentId === folder.id) { throw new ApiError(meta.errors.recursiveNesting); } else if (ps.parentId === null) { folder.parentId = null; } else { // Get parent folder - const parent = await DriveFolder - .findOne({ - _id: ps.parentId, - userId: user._id - }); + const parent = await DriveFolders.findOne({ + id: ps.parentId, + userId: user.id + }); - if (parent === null) { + if (parent == null) { throw new ApiError(meta.errors.noSuchParentFolder); } // Check if the circular reference will occur async function checkCircle(folderId: any): Promise<boolean> { // Fetch folder - const folder2 = await DriveFolder.findOne({ - _id: folderId - }, { - _id: true, - parentId: true + const folder2 = await DriveFolders.findOne({ + id: folderId }); - if (folder2._id.equals(folder._id)) { + if (folder2.id === folder.id) { return true; } else if (folder2.parentId) { return await checkCircle(folder2.parentId); @@ -124,22 +117,20 @@ export default define(meta, async (ps, user) => { } } - folder.parentId = parent._id; + folder.parentId = parent.id; } } // Update - DriveFolder.update(folder._id, { - $set: { - name: folder.name, - parentId: folder.parentId - } + DriveFolders.update(folder.id, { + name: folder.name, + parentId: folder.parentId }); - const folderObj = await pack(folder); + const folderObj = await DriveFolders.pack(folder); // Publish folderUpdated event - publishDriveStream(user._id, 'folderUpdated', folderObj); + publishDriveStream(user.id, 'folderUpdated', folderObj); return folderObj; }); diff --git a/src/server/api/endpoints/drive/stream.ts b/src/server/api/endpoints/drive/stream.ts index 916482be4d..9a84627767 100644 --- a/src/server/api/endpoints/drive/stream.ts +++ b/src/server/api/endpoints/drive/stream.ts @@ -1,14 +1,15 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import DriveFile, { packMany } from '../../../../models/drive-file'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; +import { DriveFiles } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { tags: ['drive'], requireCredential: true, - kind: 'drive-read', + kind: 'read:drive', params: { limit: { @@ -18,12 +19,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, type: { @@ -40,35 +39,18 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const sort = { - _id: -1 - }; - - const query = { - 'metadata.userId': user._id, - 'metadata.deletedAt': { $exists: false } - } as any; - - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } + const query = makePaginationQuery(DriveFiles.createQueryBuilder('file'), ps.sinceId, ps.untilId) + .andWhere('file.userId = :userId', { userId: user.id }); if (ps.type) { - query.contentType = new RegExp(`^${ps.type.replace(/\*/g, '.+?')}$`); + if (ps.type.endsWith('/*')) { + query.andWhere('file.type like :type', { type: ps.type.replace('/*', '/') + '%' }); + } else { + query.andWhere('file.type = :type', { type: ps.type }); + } } - const files = await DriveFile - .find(query, { - limit: ps.limit, - sort: sort - }); + const files = await query.take(ps.limit).getMany(); - return await packMany(files, { self: true }); + return await DriveFiles.packMany(files, { detail: false, self: true }); }); diff --git a/src/server/api/endpoints/federation/instances.ts b/src/server/api/endpoints/federation/instances.ts index f81f81822e..1946d26dec 100644 --- a/src/server/api/endpoints/federation/instances.ts +++ b/src/server/api/endpoints/federation/instances.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import define from '../../define'; -import Instance from '../../../../models/instance'; +import { Instances } from '../../../../models'; +import fetchMeta from '../../../../misc/fetch-meta'; export const meta = { tags: ['federation'], @@ -37,92 +38,55 @@ export const meta = { }; export default define(meta, async (ps, me) => { - let sort; + const query = Instances.createQueryBuilder('instance'); - if (ps.sort) { - if (ps.sort == '+notes') { - sort = { - notesCount: -1 - }; - } else if (ps.sort == '-notes') { - sort = { - notesCount: 1 - }; - } else if (ps.sort == '+users') { - sort = { - usersCount: -1 - }; - } else if (ps.sort == '-users') { - sort = { - usersCount: 1 - }; - } else if (ps.sort == '+following') { - sort = { - followingCount: -1 - }; - } else if (ps.sort == '-following') { - sort = { - followingCount: 1 - }; - } else if (ps.sort == '+followers') { - sort = { - followersCount: -1 - }; - } else if (ps.sort == '-followers') { - sort = { - followersCount: 1 - }; - } else if (ps.sort == '+caughtAt') { - sort = { - caughtAt: -1 - }; - } else if (ps.sort == '-caughtAt') { - sort = { - caughtAt: 1 - }; - } else if (ps.sort == '+lastCommunicatedAt') { - sort = { - lastCommunicatedAt: -1 - }; - } else if (ps.sort == '-lastCommunicatedAt') { - sort = { - lastCommunicatedAt: 1 - }; - } else if (ps.sort == '+driveUsage') { - sort = { - driveUsage: -1 - }; - } else if (ps.sort == '-driveUsage') { - sort = { - driveUsage: 1 - }; - } else if (ps.sort == '+driveFiles') { - sort = { - driveFiles: -1 - }; - } else if (ps.sort == '-driveFiles') { - sort = { - driveFiles: 1 - }; + switch (ps.sort) { + case '+notes': query.orderBy('instance.notesCount', 'DESC'); break; + case '-notes': query.orderBy('instance.notesCount', 'ASC'); break; + case '+usersCount': query.orderBy('instance.usersCount', 'DESC'); break; + case '-usersCount': query.orderBy('instance.usersCount', 'ASC'); break; + case '+followingCount': query.orderBy('instance.followingCount', 'DESC'); break; + case '-followingCount': query.orderBy('instance.followingCount', 'ASC'); break; + case '+followersCount': query.orderBy('instance.followersCount', 'DESC'); break; + case '-followersCount': query.orderBy('instance.followersCount', 'ASC'); break; + case '+caughtAt': query.orderBy('instance.caughtAt', 'DESC'); break; + case '-caughtAt': query.orderBy('instance.caughtAt', 'ASC'); break; + case '+lastCommunicatedAt': query.orderBy('instance.lastCommunicatedAt', 'DESC'); break; + case '-lastCommunicatedAt': query.orderBy('instance.lastCommunicatedAt', 'ASC'); break; + case '+driveUsage': query.orderBy('instance.driveUsage', 'DESC'); break; + case '-driveUsage': query.orderBy('instance.driveUsage', 'ASC'); break; + case '+driveFiles': query.orderBy('instance.driveFiles', 'DESC'); break; + case '-driveFiles': query.orderBy('instance.driveFiles', 'ASC'); break; + + default: query.orderBy('instance.id', 'DESC'); break; + } + + if (typeof ps.blocked === 'boolean') { + const meta = await fetchMeta(); + if (ps.blocked) { + query.andWhere('instance.host IN (:...blocks)', { blocks: meta.blockedHosts }); + } else { + query.andWhere('instance.host NOT IN (:...blocks)', { blocks: meta.blockedHosts }); } - } else { - sort = { - _id: -1 - }; } - const q = {} as any; + if (typeof ps.notResponding === 'boolean') { + if (ps.notResponding) { + query.andWhere('instance.isNotResponding = TRUE'); + } else { + query.andWhere('instance.isNotResponding = FALSE'); + } + } - if (typeof ps.blocked === 'boolean') q.isBlocked = ps.blocked; - if (typeof ps.notResponding === 'boolean') q.isNotResponding = ps.notResponding; - if (typeof ps.markedAsClosed === 'boolean') q.isMarkedAsClosed = ps.markedAsClosed; + if (typeof ps.markedAsClosed === 'boolean') { + if (ps.markedAsClosed) { + query.andWhere('instance.isMarkedAsClosed = TRUE'); + } else { + query.andWhere('instance.isMarkedAsClosed = FALSE'); + } + } - const instances = await Instance - .find(q, { - limit: ps.limit, - sort: sort, - skip: ps.offset - }); + const instances = await query.take(ps.limit).skip(ps.offset).getMany(); return instances; }); diff --git a/src/server/api/endpoints/federation/show-instance.ts b/src/server/api/endpoints/federation/show-instance.ts index e7f68620af..875afa05b2 100644 --- a/src/server/api/endpoints/federation/show-instance.ts +++ b/src/server/api/endpoints/federation/show-instance.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../define'; -import Instance from '../../../../models/instance'; +import { Instances } from '../../../../models'; export const meta = { tags: ['federation'], @@ -15,7 +15,7 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const instance = await Instance + const instance = await Instances .findOne({ host: ps.host }); return instance; diff --git a/src/server/api/endpoints/following/create.ts b/src/server/api/endpoints/following/create.ts index 81b2399551..5b43815a5e 100644 --- a/src/server/api/endpoints/following/create.ts +++ b/src/server/api/endpoints/following/create.ts @@ -1,12 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import * as ms from 'ms'; -import { pack } from '../../../../models/user'; -import Following from '../../../../models/following'; import create from '../../../../services/following/create'; import define from '../../define'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { Followings, Users } from '../../../../models'; export const meta = { stability: 'stable', @@ -25,12 +24,11 @@ export const meta = { requireCredential: true, - kind: 'following-write', + kind: 'write:following', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -75,7 +73,7 @@ export default define(meta, async (ps, user) => { const follower = user; // 自分自身 - if (user._id.equals(ps.userId)) { + if (user.id === ps.userId) { throw new ApiError(meta.errors.followeeIsYourself); } @@ -86,12 +84,12 @@ export default define(meta, async (ps, user) => { }); // Check if already following - const exist = await Following.findOne({ - followerId: follower._id, - followeeId: followee._id + const exist = await Followings.findOne({ + followerId: follower.id, + followeeId: followee.id }); - if (exist !== null) { + if (exist != null) { throw new ApiError(meta.errors.alreadyFollowing); } @@ -103,5 +101,5 @@ export default define(meta, async (ps, user) => { throw e; } - return await pack(followee._id, user); + return await Users.pack(followee.id, user); }); diff --git a/src/server/api/endpoints/following/delete.ts b/src/server/api/endpoints/following/delete.ts index 8f8249b1e8..240a037c9e 100644 --- a/src/server/api/endpoints/following/delete.ts +++ b/src/server/api/endpoints/following/delete.ts @@ -1,12 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import * as ms from 'ms'; -import { pack } from '../../../../models/user'; -import Following from '../../../../models/following'; import deleteFollowing from '../../../../services/following/delete'; import define from '../../define'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { Followings, Users } from '../../../../models'; export const meta = { stability: 'stable', @@ -25,12 +24,11 @@ export const meta = { requireCredential: true, - kind: 'following-write', + kind: 'write:following', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -63,7 +61,7 @@ export default define(meta, async (ps, user) => { const follower = user; // Check if the followee is yourself - if (user._id.equals(ps.userId)) { + if (user.id === ps.userId) { throw new ApiError(meta.errors.followeeIsYourself); } @@ -74,16 +72,16 @@ export default define(meta, async (ps, user) => { }); // Check not following - const exist = await Following.findOne({ - followerId: follower._id, - followeeId: followee._id + const exist = await Followings.findOne({ + followerId: follower.id, + followeeId: followee.id }); - if (exist === null) { + if (exist == null) { throw new ApiError(meta.errors.notFollowing); } await deleteFollowing(follower, followee); - return await pack(followee._id, user); + return await Users.pack(followee.id, user); }); diff --git a/src/server/api/endpoints/following/requests/accept.ts b/src/server/api/endpoints/following/requests/accept.ts index 0975990c02..65c24f7be9 100644 --- a/src/server/api/endpoints/following/requests/accept.ts +++ b/src/server/api/endpoints/following/requests/accept.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import acceptFollowRequest from '../../../../../services/following/requests/accept'; import define from '../../../define'; import { ApiError } from '../../../error'; @@ -15,12 +15,11 @@ export const meta = { requireCredential: true, - kind: 'following-write', + kind: 'write:following', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' diff --git a/src/server/api/endpoints/following/requests/cancel.ts b/src/server/api/endpoints/following/requests/cancel.ts index 371f9f0ed3..79cdb776f2 100644 --- a/src/server/api/endpoints/following/requests/cancel.ts +++ b/src/server/api/endpoints/following/requests/cancel.ts @@ -1,10 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import cancelFollowRequest from '../../../../../services/following/requests/cancel'; -import { pack } from '../../../../../models/user'; import define from '../../../define'; import { ApiError } from '../../../error'; import { getUser } from '../../../common/getters'; +import { Users } from '../../../../../models'; export const meta = { desc: { @@ -16,12 +16,11 @@ export const meta = { requireCredential: true, - kind: 'following-write', + kind: 'write:following', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -58,5 +57,5 @@ export default define(meta, async (ps, user) => { throw e; } - return await pack(followee._id, user); + return await Users.pack(followee.id, user); }); diff --git a/src/server/api/endpoints/following/requests/list.ts b/src/server/api/endpoints/following/requests/list.ts index c9bcedf929..13e4a39388 100644 --- a/src/server/api/endpoints/following/requests/list.ts +++ b/src/server/api/endpoints/following/requests/list.ts @@ -1,5 +1,5 @@ -import FollowRequest, { pack } from '../../../../../models/follow-request'; import define from '../../../define'; +import { FollowRequests } from '../../../../../models'; export const meta = { desc: { @@ -11,13 +11,13 @@ export const meta = { requireCredential: true, - kind: 'following-read' + kind: 'read:following' }; export default define(meta, async (ps, user) => { - const reqs = await FollowRequest.find({ - followeeId: user._id + const reqs = await FollowRequests.find({ + followeeId: user.id }); - return await Promise.all(reqs.map(req => pack(req))); + return await Promise.all(reqs.map(req => FollowRequests.pack(req))); }); diff --git a/src/server/api/endpoints/following/requests/reject.ts b/src/server/api/endpoints/following/requests/reject.ts index 5e59d4bc97..cccb60b243 100644 --- a/src/server/api/endpoints/following/requests/reject.ts +++ b/src/server/api/endpoints/following/requests/reject.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import rejectFollowRequest from '../../../../../services/following/requests/reject'; import define from '../../../define'; import { ApiError } from '../../../error'; @@ -15,12 +15,11 @@ export const meta = { requireCredential: true, - kind: 'following-write', + kind: 'write:following', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' diff --git a/src/server/api/endpoints/games/reversi/games.ts b/src/server/api/endpoints/games/reversi/games.ts index e3c22c7611..07736e0424 100644 --- a/src/server/api/endpoints/games/reversi/games.ts +++ b/src/server/api/endpoints/games/reversi/games.ts @@ -1,7 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import ReversiGame, { pack } from '../../../../../models/games/reversi/game'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; +import { ReversiGames } from '../../../../../models'; +import { makePaginationQuery } from '../../../common/make-pagination-query'; +import { Brackets } from 'typeorm'; export const meta = { tags: ['games'], @@ -14,12 +16,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, my: { @@ -30,39 +30,20 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const q: any = ps.my ? { - isStarted: true, - $or: [{ - user1Id: user._id - }, { - user2Id: user._id - }] - } : { - isStarted: true - }; + const query = makePaginationQuery(ReversiGames.createQueryBuilder('game'), ps.sinceId, ps.untilId) + .andWhere('game.isStarted = TRUE'); - const sort = { - _id: -1 - }; - - if (ps.sinceId) { - sort._id = 1; - q._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - q._id = { - $lt: ps.untilId - }; + if (ps.my) { + query.andWhere(new Brackets(qb => { qb + .where('game.user1Id = :userId', { userId: user.id }) + .orWhere('game.user2Id = :userId', { userId: user.id }); + })); } // Fetch games - const games = await ReversiGame.find(q, { - sort: sort, - limit: ps.limit - }); + const games = await query.take(ps.limit).getMany(); - return await Promise.all(games.map((g) => pack(g, user, { + return await Promise.all(games.map((g) => ReversiGames.pack(g, user, { detail: false }))); }); diff --git a/src/server/api/endpoints/games/reversi/games/show.ts b/src/server/api/endpoints/games/reversi/games/show.ts index 766ca90119..ea2776b16f 100644 --- a/src/server/api/endpoints/games/reversi/games/show.ts +++ b/src/server/api/endpoints/games/reversi/games/show.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../../misc/cafy-id'; -import ReversiGame, { pack } from '../../../../../../models/games/reversi/game'; +import { ID } from '../../../../../../misc/cafy-id'; import Reversi from '../../../../../../games/reversi/core'; import define from '../../../../define'; import { ApiError } from '../../../../error'; +import { ReversiGames } from '../../../../../../models'; export const meta = { tags: ['games'], @@ -11,7 +11,6 @@ export const meta = { params: { gameId: { validator: $.type(ID), - transform: transform, }, }, @@ -25,22 +24,23 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const game = await ReversiGame.findOne({ _id: ps.gameId }); + const game = await ReversiGames.findOne(ps.gameId); if (game == null) { throw new ApiError(meta.errors.noSuchGame); } - const o = new Reversi(game.settings.map, { - isLlotheo: game.settings.isLlotheo, - canPutEverywhere: game.settings.canPutEverywhere, - loopedBoard: game.settings.loopedBoard + const o = new Reversi(game.map, { + isLlotheo: game.isLlotheo, + canPutEverywhere: game.canPutEverywhere, + loopedBoard: game.loopedBoard }); - for (const log of game.logs) + for (const log of game.logs) { o.put(log.color, log.pos); + } - const packed = await pack(game, user); + const packed = await ReversiGames.pack(game, user); return Object.assign({ board: o.board, diff --git a/src/server/api/endpoints/games/reversi/games/surrender.ts b/src/server/api/endpoints/games/reversi/games/surrender.ts index 446210894d..56d66fb205 100644 --- a/src/server/api/endpoints/games/reversi/games/surrender.ts +++ b/src/server/api/endpoints/games/reversi/games/surrender.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../../misc/cafy-id'; -import ReversiGame, { pack } from '../../../../../../models/games/reversi/game'; +import { ID } from '../../../../../../misc/cafy-id'; import { publishReversiGameStream } from '../../../../../../services/stream'; import define from '../../../../define'; import { ApiError } from '../../../../error'; +import { ReversiGames } from '../../../../../../models'; export const meta = { tags: ['games'], @@ -17,7 +17,6 @@ export const meta = { params: { gameId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '投了したい対局' } @@ -46,7 +45,7 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const game = await ReversiGame.findOne({ _id: ps.gameId }); + const game = await ReversiGames.findOne(ps.gameId); if (game == null) { throw new ApiError(meta.errors.noSuchGame); @@ -56,26 +55,20 @@ export default define(meta, async (ps, user) => { throw new ApiError(meta.errors.alreadyEnded); } - if (!game.user1Id.equals(user._id) && !game.user2Id.equals(user._id)) { + if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) { throw new ApiError(meta.errors.accessDenied); } - const winnerId = game.user1Id.equals(user._id) ? game.user2Id : game.user1Id; + const winnerId = game.user1Id === user.id ? game.user2Id : game.user1Id; - await ReversiGame.update({ - _id: game._id - }, { - $set: { - surrendered: user._id, - isEnded: true, - winnerId: winnerId - } + await ReversiGames.update(game.id, { + surrendered: user.id, + isEnded: true, + winnerId: winnerId }); - publishReversiGameStream(game._id, 'ended', { + publishReversiGameStream(game.id, 'ended', { winnerId: winnerId, - game: await pack(game._id, user) + game: await ReversiGames.pack(game.id, user) }); - - return; }); diff --git a/src/server/api/endpoints/games/reversi/invitations.ts b/src/server/api/endpoints/games/reversi/invitations.ts index c204770578..71f5aca1d1 100644 --- a/src/server/api/endpoints/games/reversi/invitations.ts +++ b/src/server/api/endpoints/games/reversi/invitations.ts @@ -1,5 +1,5 @@ -import Matching, { pack as packMatching } from '../../../../../models/games/reversi/matching'; import define from '../../../define'; +import { ReversiMatchings } from '../../../../../models'; export const meta = { tags: ['games'], @@ -9,13 +9,9 @@ export const meta = { export default define(meta, async (ps, user) => { // Find session - const invitations = await Matching.find({ - childId: user._id - }, { - sort: { - _id: -1 - } + const invitations = await ReversiMatchings.find({ + childId: user.id }); - return await Promise.all(invitations.map((i) => packMatching(i, user))); + return await Promise.all(invitations.map((i) => ReversiMatchings.pack(i, user))); }); diff --git a/src/server/api/endpoints/games/reversi/match.ts b/src/server/api/endpoints/games/reversi/match.ts index e66765944d..e34d3c67f4 100644 --- a/src/server/api/endpoints/games/reversi/match.ts +++ b/src/server/api/endpoints/games/reversi/match.ts @@ -1,12 +1,14 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import Matching, { pack as packMatching } from '../../../../../models/games/reversi/matching'; -import ReversiGame, { pack as packGame } from '../../../../../models/games/reversi/game'; +import { ID } from '../../../../../misc/cafy-id'; import { publishMainStream, publishReversiStream } from '../../../../../services/stream'; import { eighteight } from '../../../../../games/reversi/maps'; import define from '../../../define'; import { ApiError } from '../../../error'; import { getUser } from '../../../common/getters'; +import { genId } from '../../../../../misc/gen-id'; +import { ReversiMatchings, ReversiGames } from '../../../../../models'; +import { ReversiGame } from '../../../../../models/entities/games/reversi/game'; +import { ReversiMatching } from '../../../../../models/entities/games/reversi/matching'; export const meta = { tags: ['games'], @@ -16,7 +18,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -41,50 +42,47 @@ export const meta = { export default define(meta, async (ps, user) => { // Myself - if (ps.userId.equals(user._id)) { + if (ps.userId === user.id) { throw new ApiError(meta.errors.isYourself); } // Find session - const exist = await Matching.findOne({ + const exist = await ReversiMatchings.findOne({ parentId: ps.userId, - childId: user._id + childId: user.id }); if (exist) { // Destroy session - Matching.remove({ - _id: exist._id - }); + ReversiMatchings.delete(exist.id); // Create game - const game = await ReversiGame.insert({ + const game = await ReversiGames.save({ + id: genId(), createdAt: new Date(), user1Id: exist.parentId, - user2Id: user._id, + user2Id: user.id, user1Accepted: false, user2Accepted: false, isStarted: false, isEnded: false, logs: [], - settings: { - map: eighteight.data, - bw: 'random', - isLlotheo: false - } - }); + map: eighteight.data, + bw: 'random', + isLlotheo: false + } as ReversiGame); - publishReversiStream(exist.parentId, 'matched', await packGame(game, exist.parentId)); + publishReversiStream(exist.parentId, 'matched', await ReversiGames.pack(game, exist.parentId)); - const other = await Matching.count({ - childId: user._id + const other = await ReversiMatchings.count({ + childId: user.id }); if (other == 0) { - publishMainStream(user._id, 'reversiNoInvites'); + publishMainStream(user.id, 'reversiNoInvites'); } - return await packGame(game, user); + return await ReversiGames.pack(game, user); } else { // Fetch child const child = await getUser(ps.userId).catch(e => { @@ -93,21 +91,22 @@ export default define(meta, async (ps, user) => { }); // 以前のセッションはすべて削除しておく - await Matching.remove({ - parentId: user._id + await ReversiMatchings.delete({ + parentId: user.id }); // セッションを作成 - const matching = await Matching.insert({ + const matching = await ReversiMatchings.save({ + id: genId(), createdAt: new Date(), - parentId: user._id, - childId: child._id - }); + parentId: user.id, + childId: child.id + } as ReversiMatching); - const packed = await packMatching(matching, child); - publishReversiStream(child._id, 'invited', packed); - publishMainStream(child._id, 'reversiInvited', packed); + const packed = await ReversiMatchings.pack(matching, child); + publishReversiStream(child.id, 'invited', packed); + publishMainStream(child.id, 'reversiInvited', packed); - return; + return null; } }); diff --git a/src/server/api/endpoints/games/reversi/match/cancel.ts b/src/server/api/endpoints/games/reversi/match/cancel.ts index fb230032d8..71aaae5ee1 100644 --- a/src/server/api/endpoints/games/reversi/match/cancel.ts +++ b/src/server/api/endpoints/games/reversi/match/cancel.ts @@ -1,5 +1,5 @@ -import Matching from '../../../../../../models/games/reversi/matching'; import define from '../../../../define'; +import { ReversiMatchings } from '../../../../../../models'; export const meta = { tags: ['games'], @@ -8,9 +8,7 @@ export const meta = { }; export default define(meta, async (ps, user) => { - await Matching.remove({ - parentId: user._id + await ReversiMatchings.delete({ + parentId: user.id }); - - return; }); diff --git a/src/server/api/endpoints/hashtags/list.ts b/src/server/api/endpoints/hashtags/list.ts index f454d47fed..7996c81669 100644 --- a/src/server/api/endpoints/hashtags/list.ts +++ b/src/server/api/endpoints/hashtags/list.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../define'; -import Hashtag from '../../../../models/hashtag'; +import { Hashtags } from '../../../../models'; export const meta = { tags: ['hashtags'], @@ -54,40 +54,39 @@ export const meta = { }, }; -const sort: any = { - '+mentionedUsers': { mentionedUsersCount: -1 }, - '-mentionedUsers': { mentionedUsersCount: 1 }, - '+mentionedLocalUsers': { mentionedLocalUsersCount: -1 }, - '-mentionedLocalUsers': { mentionedLocalUsersCount: 1 }, - '+mentionedRemoteUsers': { mentionedRemoteUsersCount: -1 }, - '-mentionedRemoteUsers': { mentionedRemoteUsersCount: 1 }, - '+attachedUsers': { attachedUsersCount: -1 }, - '-attachedUsers': { attachedUsersCount: 1 }, - '+attachedLocalUsers': { attachedLocalUsersCount: -1 }, - '-attachedLocalUsers': { attachedLocalUsersCount: 1 }, - '+attachedRemoteUsers': { attachedRemoteUsersCount: -1 }, - '-attachedRemoteUsers': { attachedRemoteUsersCount: 1 }, -}; - export default define(meta, async (ps, me) => { - const q = {} as any; - if (ps.attachedToUserOnly) q.attachedUsersCount = { $ne: 0 }; - if (ps.attachedToLocalUserOnly) q.attachedLocalUsersCount = { $ne: 0 }; - if (ps.attachedToRemoteUserOnly) q.attachedRemoteUsersCount = { $ne: 0 }; - const tags = await Hashtag - .find(q, { - limit: ps.limit, - sort: sort[ps.sort], - fields: { - tag: true, - mentionedUsersCount: true, - mentionedLocalUsersCount: true, - mentionedRemoteUsersCount: true, - attachedUsersCount: true, - attachedLocalUsersCount: true, - attachedRemoteUsersCount: true - } - }); + const query = Hashtags.createQueryBuilder('tag'); + + if (ps.attachedToUserOnly) query.andWhere('tag.attachedUsersCount != 0'); + if (ps.attachedToLocalUserOnly) query.andWhere('tag.attachedLocalUsersCount != 0'); + if (ps.attachedToRemoteUserOnly) query.andWhere('tag.attachedRemoteUsersCount != 0'); + + switch (ps.sort) { + case '+mentionedUsers': query.orderBy('tag.mentionedUsersCount', 'DESC'); break; + case '-mentionedUsers': query.orderBy('tag.mentionedUsersCount', 'ASC'); break; + case '+mentionedLocalUsers': query.orderBy('tag.mentionedLocalUsersCount', 'DESC'); break; + case '-mentionedLocalUsers': query.orderBy('tag.mentionedLocalUsersCount', 'ASC'); break; + case '+mentionedRemoteUsers': query.orderBy('tag.mentionedRemoteUsersCount', 'DESC'); break; + case '-mentionedRemoteUsers': query.orderBy('tag.mentionedRemoteUsersCount', 'ASC'); break; + case '+attachedUsers': query.orderBy('tag.attachedUsersCount', 'DESC'); break; + case '-attachedUsers': query.orderBy('tag.attachedUsersCount', 'ASC'); break; + case '+attachedLocalUsers': query.orderBy('tag.attachedLocalUsersCount', 'DESC'); break; + case '-attachedLocalUsers': query.orderBy('tag.attachedLocalUsersCount', 'ASC'); break; + case '+attachedRemoteUsers': query.orderBy('tag.attachedRemoteUsersCount', 'DESC'); break; + case '-attachedRemoteUsers': query.orderBy('tag.attachedRemoteUsersCount', 'ASC'); break; + } + + query.select([ + 'tag.name', + 'tag.mentionedUsersCount', + 'tag.mentionedLocalUsersCount', + 'tag.mentionedRemoteUsersCount', + 'tag.attachedUsersCount', + 'tag.attachedLocalUsersCount', + 'tag.attachedRemoteUsersCount', + ]); + + const tags = await query.take(ps.limit).getMany(); return tags; }); diff --git a/src/server/api/endpoints/hashtags/search.ts b/src/server/api/endpoints/hashtags/search.ts index 19b2975adf..fd91a2ebcc 100644 --- a/src/server/api/endpoints/hashtags/search.ts +++ b/src/server/api/endpoints/hashtags/search.ts @@ -1,7 +1,6 @@ import $ from 'cafy'; -import Hashtag from '../../../../models/hashtag'; import define from '../../define'; -import * as escapeRegexp from 'escape-regexp'; +import { Hashtags } from '../../../../models'; export const meta = { desc: { @@ -46,16 +45,12 @@ export const meta = { }; export default define(meta, async (ps) => { - const hashtags = await Hashtag - .find({ - tag: new RegExp('^' + escapeRegexp(ps.query.toLowerCase())) - }, { - sort: { - count: -1 - }, - limit: ps.limit, - skip: ps.offset - }); + const hashtags = await Hashtags.createQueryBuilder('tag') + .where('tag.name like :q', { q: ps.query.toLowerCase() + '%' }) + .orderBy('tag.count', 'DESC') + .take(ps.limit) + .skip(ps.offset) + .getMany(); - return hashtags.map(tag => tag.tag); + return hashtags.map(tag => tag.name); }); diff --git a/src/server/api/endpoints/hashtags/trend.ts b/src/server/api/endpoints/hashtags/trend.ts index 8b8dd70245..c750e72a15 100644 --- a/src/server/api/endpoints/hashtags/trend.ts +++ b/src/server/api/endpoints/hashtags/trend.ts @@ -1,17 +1,19 @@ -import Note from '../../../../models/note'; -import { erase } from '../../../../prelude/array'; import define from '../../define'; import fetchMeta from '../../../../misc/fetch-meta'; +import { Notes } from '../../../../models'; +import { Note } from '../../../../models/entities/note'; /* トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要 ユニーク投稿数とはそのハッシュタグと投稿ユーザーのペアのカウントで、例えば同じユーザーが複数回同じハッシュタグを投稿してもそのハッシュタグのユニーク投稿数は1とカウントされる + +..が理想だけどPostgreSQLでどうするのか分からないので単に「直近Aの内に投稿されたユニーク投稿数が多いハッシュタグ」で妥協する */ const rangeA = 1000 * 60 * 30; // 30分 -const rangeB = 1000 * 60 * 120; // 2時間 -const coefficient = 1.25; // 「n倍」の部分 -const requiredUsers = 3; // 最低何人がそのタグを投稿している必要があるか +//const rangeB = 1000 * 60 * 120; // 2時間 +//const coefficient = 1.25; // 「n倍」の部分 +//const requiredUsers = 3; // 最低何人がそのタグを投稿している必要があるか const max = 5; @@ -23,92 +25,47 @@ export const meta = { export default define(meta, async () => { const instance = await fetchMeta(); - const hidedTags = instance.hidedTags.map(t => t.toLowerCase()); + const hiddenTags = instance.hiddenTags.map(t => t.toLowerCase()); - //#region 1. 直近Aの内に投稿されたハッシュタグ(とユーザーのペア)を集計 - const data = await Note.aggregate([{ - $match: { - createdAt: { - $gt: new Date(Date.now() - rangeA) - }, - tagsLower: { - $exists: true, - $ne: [] - } - } - }, { - $unwind: '$tagsLower' - }, { - $group: { - _id: { tag: '$tagsLower', userId: '$userId' } - } - }]) as { - _id: { - tag: string; - userId: any; - } - }[]; - //#endregion + const tagNotes = await Notes.createQueryBuilder('note') + .where(`note.createdAt > :date`, { date: new Date(Date.now() - rangeA) }) + .andWhere(`note.tags != '{}'`) + .select(['note.tags', 'note.userId']) + .getMany(); - if (data.length == 0) { + if (tagNotes.length === 0) { return []; } const tags: { name: string; - count: number; + users: Note['userId'][]; }[] = []; - // カウント - for (const x of data.map(x => x._id).filter(x => !hidedTags.includes(x.tag))) { - const i = tags.findIndex(tag => tag.name == x.tag); - if (i != -1) { - tags[i].count++; - } else { - tags.push({ - name: x.tag, - count: 1 - }); - } - } - - // 最低要求投稿者数を下回るならカットする - const limitedTags = tags.filter(tag => tag.count >= requiredUsers); + for (const note of tagNotes) { + for (const tag of note.tags) { + if (hiddenTags.includes(tag)) continue; - //#region 2. 1で取得したそれぞれのタグについて、「直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上」かどうかを判定する - const hotsPromises = limitedTags.map(async tag => { - const passedCount = (await Note.distinct('userId', { - tagsLower: tag.name, - createdAt: { - $lt: new Date(Date.now() - rangeA), - $gt: new Date(Date.now() - rangeB) + const x = tags.find(x => x.name === tag); + if (x) { + if (!x.users.includes(note.userId)) { + x.users.push(note.userId); + } + } else { + tags.push({ + name: tag, + users: [note.userId] + }); } - }) as any).length; - - if (tag.count >= (passedCount * coefficient)) { - return tag; - } else { - return null; } - }); - //#endregion + } // タグを人気順に並べ替え - let hots = erase(null, await Promise.all(hotsPromises)) - .sort((a, b) => b.count - a.count) + const hots = tags + .sort((a, b) => b.users.length - a.users.length) .map(tag => tag.name) .slice(0, max); - //#region 3. もし上記の方法でのトレンド抽出の結果、求められているタグ数に達しなければ「ただ単に現在投稿数が多いハッシュタグ」に切り替える - if (hots.length < max) { - hots = hots.concat(tags - .filter(tag => hots.indexOf(tag.name) == -1) - .sort((a, b) => b.count - a.count) - .map(tag => tag.name) - .slice(0, max - hots.length)); - } - //#endregion - //#region 2(または3)で話題と判定されたタグそれぞれについて過去の投稿数グラフを取得する const countPromises: Promise<any[]>[] = []; @@ -118,23 +75,25 @@ export default define(meta, async () => { const interval = 1000 * 60 * 10; for (let i = 0; i < range; i++) { - countPromises.push(Promise.all(hots.map(tag => Note.distinct('userId', { - tagsLower: tag, - createdAt: { - $lt: new Date(Date.now() - (interval * i)), - $gt: new Date(Date.now() - (interval * (i + 1))) - } - })))); + countPromises.push(Promise.all(hots.map(tag => Notes.createQueryBuilder('note') + .select('count(distinct note.userId)') + .where(':tag = ANY(note.tags)', { tag: tag }) + .andWhere('note.createdAt < :lt', { lt: new Date(Date.now() - (interval * i)) }) + .andWhere('note.createdAt > :gt', { gt: new Date(Date.now() - (interval * (i + 1))) }) + .getRawOne() + .then(x => parseInt(x.count, 10)) + ))); } const countsLog = await Promise.all(countPromises); - const totalCounts: any = await Promise.all(hots.map(tag => Note.distinct('userId', { - tagsLower: tag, - createdAt: { - $gt: new Date(Date.now() - (interval * range)) - } - }))); + const totalCounts: any = await Promise.all(hots.map(tag => Notes.createQueryBuilder('note') + .select('count(distinct note.userId)') + .where(':tag = ANY(note.tags)', { tag: tag }) + .andWhere('note.createdAt > :gt', { gt: new Date(Date.now() - (interval * range)) }) + .getRawOne() + .then(x => parseInt(x.count, 10)) + )); //#endregion const stats = hots.map((tag, i) => ({ diff --git a/src/server/api/endpoints/hashtags/users.ts b/src/server/api/endpoints/hashtags/users.ts index 4b047aee95..20cba96d0e 100644 --- a/src/server/api/endpoints/hashtags/users.ts +++ b/src/server/api/endpoints/hashtags/users.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; -import User, { pack } from '../../../../models/user'; import define from '../../define'; +import { Users } from '../../../../models'; export const meta = { requireCredential: false, @@ -54,39 +54,32 @@ export const meta = { }, }; -const sort: any = { - '+follower': { followersCount: -1 }, - '-follower': { followersCount: 1 }, - '+createdAt': { createdAt: -1 }, - '-createdAt': { createdAt: 1 }, - '+updatedAt': { updatedAt: -1 }, - '-updatedAt': { updatedAt: 1 }, -}; - export default define(meta, async (ps, me) => { - const q = { - tags: ps.tag, - $and: [] - } as any; + const query = Users.createQueryBuilder('user') + .where(':tag = ANY(user.tags)', { tag: ps.tag }); + + const recent = new Date(Date.now() - (1000 * 60 * 60 * 24 * 5)); + + if (ps.state === 'alive') { + query.andWhere('user.updatedAt > :date', { date: recent }); + } - // state - q.$and.push( - ps.state == 'alive' ? { updatedAt: { $gt: new Date(Date.now() - (1000 * 60 * 60 * 24 * 5)) } } : - {} - ); + if (ps.origin === 'local') { + query.andWhere('user.host IS NULL'); + } else if (ps.origin === 'remote') { + query.andWhere('user.host IS NOT NULL'); + } - // origin - q.$and.push( - ps.origin == 'local' ? { host: null } : - ps.origin == 'remote' ? { host: { $ne: null } } : - {} - ); + switch (ps.sort) { + case '+follower': query.orderBy('user.followersCount', 'DESC'); break; + case '-follower': query.orderBy('user.followersCount', 'ASC'); break; + case '+createdAt': query.orderBy('user.createdAt', 'DESC'); break; + case '-createdAt': query.orderBy('user.createdAt', 'ASC'); break; + case '+updatedAt': query.orderBy('user.updatedAt', 'DESC'); break; + case '-updatedAt': query.orderBy('user.updatedAt', 'ASC'); break; + } - const users = await User - .find(q, { - limit: ps.limit, - sort: sort[ps.sort], - }); + const users = await query.take(ps.limit).getMany(); - return await Promise.all(users.map(user => pack(user, me, { detail: true }))); + return await Users.packMany(users, me, { detail: true }); }); diff --git a/src/server/api/endpoints/i.ts b/src/server/api/endpoints/i.ts index 7b50cc76c2..afad38c469 100644 --- a/src/server/api/endpoints/i.ts +++ b/src/server/api/endpoints/i.ts @@ -1,5 +1,5 @@ -import { pack } from '../../../models/user'; import define from '../define'; +import { Users } from '../../../models'; export const meta = { stability: 'stable', @@ -22,7 +22,7 @@ export const meta = { export default define(meta, async (ps, user, app) => { const isSecure = user != null && app == null; - return await pack(user, user, { + return await Users.pack(user, user, { detail: true, includeHasUnreadNotes: true, includeSecrets: isSecure diff --git a/src/server/api/endpoints/i/2fa/done.ts b/src/server/api/endpoints/i/2fa/done.ts index 556354c386..8ccb09b8b7 100644 --- a/src/server/api/endpoints/i/2fa/done.ts +++ b/src/server/api/endpoints/i/2fa/done.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import * as speakeasy from 'speakeasy'; -import User from '../../../../../models/user'; import define from '../../../define'; +import { Users } from '../../../../../models'; export const meta = { requireCredential: true, @@ -32,12 +32,8 @@ export default define(meta, async (ps, user) => { throw new Error('not verified'); } - await User.update(user._id, { - $set: { - 'twoFactorSecret': user.twoFactorTempSecret, - 'twoFactorEnabled': true - } + await Users.update(user.id, { + twoFactorSecret: user.twoFactorTempSecret, + twoFactorEnabled: true }); - - return; }); diff --git a/src/server/api/endpoints/i/2fa/register.ts b/src/server/api/endpoints/i/2fa/register.ts index 302b51ec0b..5efe77900a 100644 --- a/src/server/api/endpoints/i/2fa/register.ts +++ b/src/server/api/endpoints/i/2fa/register.ts @@ -2,9 +2,9 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; import * as QRCode from 'qrcode'; -import User from '../../../../../models/user'; import config from '../../../../../config'; import define from '../../../define'; +import { Users } from '../../../../../models'; export const meta = { requireCredential: true, @@ -31,10 +31,8 @@ export default define(meta, async (ps, user) => { length: 32 }); - await User.update(user._id, { - $set: { - twoFactorTempSecret: secret.base32 - } + await Users.update(user.id, { + twoFactorTempSecret: secret.base32 }); // Get the data URL of the authenticator URL diff --git a/src/server/api/endpoints/i/2fa/unregister.ts b/src/server/api/endpoints/i/2fa/unregister.ts index 37b2639198..fb3ecd4043 100644 --- a/src/server/api/endpoints/i/2fa/unregister.ts +++ b/src/server/api/endpoints/i/2fa/unregister.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; -import User from '../../../../../models/user'; import define from '../../../define'; +import { Users } from '../../../../../models'; export const meta = { requireCredential: true, @@ -23,11 +23,9 @@ export default define(meta, async (ps, user) => { throw new Error('incorrect password'); } - await User.update(user._id, { - $set: { - 'twoFactorSecret': null, - 'twoFactorEnabled': false - } + await Users.update(user.id, { + twoFactorSecret: null, + twoFactorEnabled: false }); return; diff --git a/src/server/api/endpoints/i/authorized-apps.ts b/src/server/api/endpoints/i/authorized-apps.ts index cb8be9ed97..ebf04fcb58 100644 --- a/src/server/api/endpoints/i/authorized-apps.ts +++ b/src/server/api/endpoints/i/authorized-apps.ts @@ -1,7 +1,6 @@ import $ from 'cafy'; -import AccessToken from '../../../../models/access-token'; -import { pack } from '../../../../models/app'; import define from '../../define'; +import { AccessTokens, Apps } from '../../../../models'; export const meta = { requireCredential: true, @@ -28,18 +27,18 @@ export const meta = { export default define(meta, async (ps, user) => { // Get tokens - const tokens = await AccessToken - .find({ - userId: user._id - }, { - limit: ps.limit, - skip: ps.offset, - sort: { - _id: ps.sort == 'asc' ? 1 : -1 - } - }); + const tokens = await AccessTokens.find({ + where: { + userId: user.id + }, + take: ps.limit, + skip: ps.offset, + order: { + id: ps.sort == 'asc' ? 1 : -1 + } + }); - return await Promise.all(tokens.map(token => pack(token.appId, user, { + return await Promise.all(tokens.map(token => Apps.pack(token.appId, user, { detail: true }))); }); diff --git a/src/server/api/endpoints/i/change-password.ts b/src/server/api/endpoints/i/change-password.ts index 8ab286b4bf..f8f977200f 100644 --- a/src/server/api/endpoints/i/change-password.ts +++ b/src/server/api/endpoints/i/change-password.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; -import User from '../../../../models/user'; import define from '../../define'; +import { Users } from '../../../../models'; export const meta = { requireCredential: true, @@ -31,11 +31,7 @@ export default define(meta, async (ps, user) => { const salt = await bcrypt.genSalt(8); const hash = await bcrypt.hash(ps.newPassword, salt); - await User.update(user._id, { - $set: { - 'password': hash - } + await Users.update(user.id, { + password: hash }); - - return; }); diff --git a/src/server/api/endpoints/i/clear-follow-request-notification.ts b/src/server/api/endpoints/i/clear-follow-request-notification.ts deleted file mode 100644 index 38c6ec1cef..0000000000 --- a/src/server/api/endpoints/i/clear-follow-request-notification.ts +++ /dev/null @@ -1,23 +0,0 @@ -import User from '../../../../models/user'; -import define from '../../define'; - -export const meta = { - tags: ['account', 'following'], - - requireCredential: true, - - kind: 'account-write', - - params: { - } -}; - -export default define(meta, async (ps, user) => { - await User.update({ _id: user._id }, { - $set: { - pendingReceivedFollowRequestsCount: 0 - } - }); - - return; -}); diff --git a/src/server/api/endpoints/i/delete-account.ts b/src/server/api/endpoints/i/delete-account.ts index fed38eab5a..5aff74e0cc 100644 --- a/src/server/api/endpoints/i/delete-account.ts +++ b/src/server/api/endpoints/i/delete-account.ts @@ -1,10 +1,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; -import User from '../../../../models/user'; import define from '../../define'; -import { createDeleteNotesJob, createDeleteDriveFilesJob } from '../../../../queue'; -import Message from '../../../../models/messaging-message'; -import Signin from '../../../../models/signin'; +import { Users } from '../../../../models'; export const meta = { requireCredential: true, @@ -26,27 +23,5 @@ export default define(meta, async (ps, user) => { throw new Error('incorrect password'); } - await User.update({ _id: user._id }, { - $set: { - isDeleted: true, - name: null, - description: null, - pinnedNoteIds: [], - password: null, - email: null, - twitter: null, - github: null, - discord: null, - profile: {}, - fields: [], - clientSettings: {}, - } - }); - - Message.remove({ userId: user._id }); - Signin.remove({ userId: user._id }); - createDeleteNotesJob(user); - createDeleteDriveFilesJob(user); - - return; + await Users.delete(user.id); }); diff --git a/src/server/api/endpoints/i/export-blocking.ts b/src/server/api/endpoints/i/export-blocking.ts index 346b29c79d..14d49487e8 100644 --- a/src/server/api/endpoints/i/export-blocking.ts +++ b/src/server/api/endpoints/i/export-blocking.ts @@ -13,6 +13,4 @@ export const meta = { export default define(meta, async (ps, user) => { createExportBlockingJob(user); - - return; }); diff --git a/src/server/api/endpoints/i/export-following.ts b/src/server/api/endpoints/i/export-following.ts index 5977b03105..50dd28837f 100644 --- a/src/server/api/endpoints/i/export-following.ts +++ b/src/server/api/endpoints/i/export-following.ts @@ -13,6 +13,4 @@ export const meta = { export default define(meta, async (ps, user) => { createExportFollowingJob(user); - - return; }); diff --git a/src/server/api/endpoints/i/export-mute.ts b/src/server/api/endpoints/i/export-mute.ts index 22ceff3631..1eb51cd77e 100644 --- a/src/server/api/endpoints/i/export-mute.ts +++ b/src/server/api/endpoints/i/export-mute.ts @@ -13,6 +13,4 @@ export const meta = { export default define(meta, async (ps, user) => { createExportMuteJob(user); - - return; }); diff --git a/src/server/api/endpoints/i/export-notes.ts b/src/server/api/endpoints/i/export-notes.ts index 2881aa2697..dd32c18d11 100644 --- a/src/server/api/endpoints/i/export-notes.ts +++ b/src/server/api/endpoints/i/export-notes.ts @@ -13,6 +13,4 @@ export const meta = { export default define(meta, async (ps, user) => { createExportNotesJob(user); - - return; }); diff --git a/src/server/api/endpoints/i/export-user-lists.ts b/src/server/api/endpoints/i/export-user-lists.ts index 9d7424ad89..7650ca7210 100644 --- a/src/server/api/endpoints/i/export-user-lists.ts +++ b/src/server/api/endpoints/i/export-user-lists.ts @@ -13,6 +13,4 @@ export const meta = { export default define(meta, async (ps, user) => { createExportUserListsJob(user); - - return; }); diff --git a/src/server/api/endpoints/i/favorites.ts b/src/server/api/endpoints/i/favorites.ts index 7ea6f7b966..d2d149b2d1 100644 --- a/src/server/api/endpoints/i/favorites.ts +++ b/src/server/api/endpoints/i/favorites.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Favorite, { packMany } from '../../../../models/favorite'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; +import { NoteFavorites } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { desc: { @@ -23,42 +24,22 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, - } + }, } }; export default define(meta, async (ps, user) => { - const query = { - userId: user._id - } as any; - - const sort = { - _id: -1 - }; - - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } + const query = makePaginationQuery(NoteFavorites.createQueryBuilder('favorite'), ps.sinceId, ps.untilId) + .andWhere(`favorite.userId = :meId`, { meId: user.id }) + .leftJoinAndSelect('favorite.note', 'note'); - // Get favorites - const favorites = await Favorite - .find(query, { - limit: ps.limit, - sort: sort - }); + const favorites = await query + .take(ps.limit) + .getMany(); - return await packMany(favorites, user); + return await NoteFavorites.packMany(favorites, user); }); diff --git a/src/server/api/endpoints/i/import-following.ts b/src/server/api/endpoints/i/import-following.ts index f188291bc2..deafec18ec 100644 --- a/src/server/api/endpoints/i/import-following.ts +++ b/src/server/api/endpoints/i/import-following.ts @@ -1,10 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { createImportFollowingJob } from '../../../../queue'; import ms = require('ms'); -import DriveFile from '../../../../models/drive-file'; import { ApiError } from '../../error'; +import { DriveFiles } from '../../../../models'; export const meta = { secure: true, @@ -17,7 +17,6 @@ export const meta = { params: { fileId: { validator: $.type(ID), - transform: transform, } }, @@ -49,16 +48,12 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const file = await DriveFile.findOne({ - _id: ps.fileId - }); + const file = await DriveFiles.findOne(ps.fileId); if (file == null) throw new ApiError(meta.errors.noSuchFile); - //if (!file.contentType.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); - if (file.length > 50000) throw new ApiError(meta.errors.tooBigFile); - if (file.length === 0) throw new ApiError(meta.errors.emptyFile); + //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); + if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile); + if (file.size === 0) throw new ApiError(meta.errors.emptyFile); - createImportFollowingJob(user, file._id); - - return; + createImportFollowingJob(user, file.id); }); diff --git a/src/server/api/endpoints/i/import-user-lists.ts b/src/server/api/endpoints/i/import-user-lists.ts index ed3085e5f8..b7d9d029b7 100644 --- a/src/server/api/endpoints/i/import-user-lists.ts +++ b/src/server/api/endpoints/i/import-user-lists.ts @@ -1,10 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { createImportUserListsJob } from '../../../../queue'; import ms = require('ms'); -import DriveFile from '../../../../models/drive-file'; import { ApiError } from '../../error'; +import { DriveFiles } from '../../../../models'; export const meta = { secure: true, @@ -17,7 +17,6 @@ export const meta = { params: { fileId: { validator: $.type(ID), - transform: transform, } }, @@ -49,16 +48,12 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const file = await DriveFile.findOne({ - _id: ps.fileId - }); + const file = await DriveFiles.findOne(ps.fileId); if (file == null) throw new ApiError(meta.errors.noSuchFile); - //if (!file.contentType.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); - if (file.length > 30000) throw new ApiError(meta.errors.tooBigFile); - if (file.length === 0) throw new ApiError(meta.errors.emptyFile); + //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); + if (file.size > 30000) throw new ApiError(meta.errors.tooBigFile); + if (file.size === 0) throw new ApiError(meta.errors.emptyFile); - createImportUserListsJob(user, file._id); - - return; + createImportUserListsJob(user, file.id); }); diff --git a/src/server/api/endpoints/i/notifications.ts b/src/server/api/endpoints/i/notifications.ts index d3e3064abd..9b016e0a2d 100644 --- a/src/server/api/endpoints/i/notifications.ts +++ b/src/server/api/endpoints/i/notifications.ts @@ -1,11 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Notification from '../../../../models/notification'; -import { packMany } from '../../../../models/notification'; -import { getFriendIds } from '../../common/get-friends'; -import read from '../../common/read-notification'; +import { ID } from '../../../../misc/cafy-id'; +import { readNotification } from '../../common/read-notification'; import define from '../../define'; -import { getHideUserIds } from '../../common/get-hide-users'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { Notifications, Followings, Mutings } from '../../../../models'; export const meta = { desc: { @@ -17,7 +15,7 @@ export const meta = { requireCredential: true, - kind: 'account-read', + kind: 'read:notifications', params: { limit: { @@ -27,12 +25,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, following: { @@ -46,12 +42,12 @@ export const meta = { }, includeTypes: { - validator: $.optional.arr($.str.or(['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'poll_vote', 'receiveFollowRequest'])), + validator: $.optional.arr($.str.or(['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest'])), default: [] as string[] }, excludeTypes: { - validator: $.optional.arr($.str.or(['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'poll_vote', 'receiveFollowRequest'])), + validator: $.optional.arr($.str.or(['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest'])), default: [] as string[] } }, @@ -65,63 +61,38 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const hideUserIds = await getHideUserIds(user); + const followingQuery = Followings.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: user.id }); - const query = { - notifieeId: user._id, - $and: [{ - notifierId: { - $nin: hideUserIds - } - }] - } as any; + const mutingQuery = Mutings.createQueryBuilder('muting') + .select('muting.muteeId') + .where('muting.muterId = :muterId', { muterId: user.id }); - const sort = { - _id: -1 - }; + const query = makePaginationQuery(Notifications.createQueryBuilder('notification'), ps.sinceId, ps.untilId) + .andWhere(`notification.notifieeId = :meId`, { meId: user.id }) + .leftJoinAndSelect('notification.notifier', 'notifier'); - if (ps.following) { - // ID list of the user itself and other users who the user follows - const followingIds = await getFriendIds(user._id); - - query.$and.push({ - notifierId: { - $in: followingIds - } - }); - } + query.andWhere(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`); + query.setParameters(mutingQuery.getParameters()); - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; + if (ps.following) { + query.andWhere(`((notification.notifierId IN (${ followingQuery.getQuery() })) OR (notification.notifierId = :meId))`, { meId: user.id }); + query.setParameters(followingQuery.getParameters()); } if (ps.includeTypes.length > 0) { - query.type = { - $in: ps.includeTypes - }; + query.andWhere(`notification.type IN (:...includeTypes)`, { includeTypes: ps.includeTypes }); } else if (ps.excludeTypes.length > 0) { - query.type = { - $nin: ps.excludeTypes - }; + query.andWhere(`notification.type NOT IN (:...excludeTypes)`, { excludeTypes: ps.excludeTypes }); } - const notifications = await Notification - .find(query, { - limit: ps.limit, - sort: sort - }); + const notifications = await query.take(ps.limit).getMany(); // Mark all as read if (notifications.length > 0 && ps.markAsRead) { - read(user._id, notifications); + readNotification(user.id, notifications.map(x => x.id)); } - return await packMany(notifications); + return await Notifications.packMany(notifications); }); diff --git a/src/server/api/endpoints/i/pin.ts b/src/server/api/endpoints/i/pin.ts index 8d853d45c8..ac104b19f9 100644 --- a/src/server/api/endpoints/i/pin.ts +++ b/src/server/api/endpoints/i/pin.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import { pack } from '../../../../models/user'; +import { ID } from '../../../../misc/cafy-id'; import { addPinned } from '../../../../services/i/pin'; import define from '../../define'; import { ApiError } from '../../error'; +import { Users } from '../../../../models'; export const meta = { stability: 'stable', @@ -16,12 +16,11 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID' @@ -58,7 +57,7 @@ export default define(meta, async (ps, user) => { throw e; }); - return await pack(user, user, { + return await Users.pack(user, user, { detail: true }); }); diff --git a/src/server/api/endpoints/i/read-all-messaging-messages.ts b/src/server/api/endpoints/i/read-all-messaging-messages.ts index bbbfa0d7b3..e8ada277e9 100644 --- a/src/server/api/endpoints/i/read-all-messaging-messages.ts +++ b/src/server/api/endpoints/i/read-all-messaging-messages.ts @@ -1,7 +1,6 @@ -import User from '../../../../models/user'; import { publishMainStream } from '../../../../services/stream'; -import Message from '../../../../models/messaging-message'; import define from '../../define'; +import { MessagingMessages } from '../../../../models'; export const meta = { desc: { @@ -13,7 +12,7 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { } @@ -21,24 +20,12 @@ export const meta = { export default define(meta, async (ps, user) => { // Update documents - await Message.update({ - recipientId: user._id, + await MessagingMessages.update({ + recipientId: user.id, isRead: false }, { - $set: { - isRead: true - } - }, { - multi: true - }); - - User.update({ _id: user._id }, { - $set: { - hasUnreadMessagingMessage: false - } + isRead: true }); - publishMainStream(user._id, 'readAllMessagingMessages'); - - return; + publishMainStream(user.id, 'readAllMessagingMessages'); }); diff --git a/src/server/api/endpoints/i/read-all-unread-notes.ts b/src/server/api/endpoints/i/read-all-unread-notes.ts index 742c2d9908..cc8ebf58ec 100644 --- a/src/server/api/endpoints/i/read-all-unread-notes.ts +++ b/src/server/api/endpoints/i/read-all-unread-notes.ts @@ -1,7 +1,6 @@ -import User from '../../../../models/user'; import { publishMainStream } from '../../../../services/stream'; -import NoteUnread from '../../../../models/note-unread'; import define from '../../define'; +import { NoteUnreads } from '../../../../models'; export const meta = { desc: { @@ -13,7 +12,7 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { } @@ -21,20 +20,11 @@ export const meta = { export default define(meta, async (ps, user) => { // Remove documents - await NoteUnread.remove({ - userId: user._id - }); - - User.update({ _id: user._id }, { - $set: { - hasUnreadMentions: false, - hasUnreadSpecifiedNotes: false - } + await NoteUnreads.delete({ + userId: user.id }); // 全て既読になったイベントを発行 - publishMainStream(user._id, 'readAllUnreadMentions'); - publishMainStream(user._id, 'readAllUnreadSpecifiedNotes'); - - return; + publishMainStream(user.id, 'readAllUnreadMentions'); + publishMainStream(user.id, 'readAllUnreadSpecifiedNotes'); }); diff --git a/src/server/api/endpoints/i/regenerate-token.ts b/src/server/api/endpoints/i/regenerate-token.ts index ad10b99b36..729c1a300a 100644 --- a/src/server/api/endpoints/i/regenerate-token.ts +++ b/src/server/api/endpoints/i/regenerate-token.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; -import User from '../../../../models/user'; import { publishMainStream } from '../../../../services/stream'; import generateUserToken from '../../common/generate-native-user-token'; import define from '../../define'; +import { Users } from '../../../../models'; export const meta = { requireCredential: true, @@ -28,14 +28,10 @@ export default define(meta, async (ps, user) => { // Generate secret const secret = generateUserToken(); - await User.update(user._id, { - $set: { - 'token': secret - } + await Users.update(user.id, { + token: secret }); // Publish event - publishMainStream(user._id, 'myTokenRegenerated'); - - return; + publishMainStream(user.id, 'myTokenRegenerated'); }); diff --git a/src/server/api/endpoints/i/signin-history.ts b/src/server/api/endpoints/i/signin-history.ts index 87160a9f91..e9ae19d734 100644 --- a/src/server/api/endpoints/i/signin-history.ts +++ b/src/server/api/endpoints/i/signin-history.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Signin, { pack } from '../../../../models/signin'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; +import { Signins } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { requireCredential: true, @@ -16,41 +17,19 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, } } }; export default define(meta, async (ps, user) => { - const query = { - userId: user._id - } as any; + const query = makePaginationQuery(Signins.createQueryBuilder('signin'), ps.sinceId, ps.untilId) + .andWhere(`signin.userId = :meId`, { meId: user.id }); - const sort = { - _id: -1 - }; + const history = await query.take(ps.limit).getMany(); - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } - - const history = await Signin - .find(query, { - limit: ps.limit, - sort: sort - }); - - return await Promise.all(history.map(record => pack(record))); + return await Promise.all(history.map(record => Signins.pack(record))); }); diff --git a/src/server/api/endpoints/i/unpin.ts b/src/server/api/endpoints/i/unpin.ts index 184d46f2c3..4688533578 100644 --- a/src/server/api/endpoints/i/unpin.ts +++ b/src/server/api/endpoints/i/unpin.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import { pack } from '../../../../models/user'; +import { ID } from '../../../../misc/cafy-id'; import { removePinned } from '../../../../services/i/pin'; import define from '../../define'; import { ApiError } from '../../error'; +import { Users } from '../../../../models'; export const meta = { stability: 'stable', @@ -16,12 +16,11 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID' @@ -44,7 +43,7 @@ export default define(meta, async (ps, user) => { throw e; }); - return await pack(user, user, { + return await Users.pack(user, user, { detail: true }); }); diff --git a/src/server/api/endpoints/i/update-client-setting.ts b/src/server/api/endpoints/i/update-client-setting.ts index 79cd04e169..edbfe28f35 100644 --- a/src/server/api/endpoints/i/update-client-setting.ts +++ b/src/server/api/endpoints/i/update-client-setting.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import User from '../../../../models/user'; import { publishMainStream } from '../../../../services/stream'; import define from '../../define'; +import { Users } from '../../../../models'; export const meta = { requireCredential: true, @@ -10,7 +10,7 @@ export const meta = { params: { name: { - validator: $.str + validator: $.str.match(/^[a-zA-Z]+$/) }, value: { @@ -20,18 +20,18 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const x: any = {}; - x[`clientSettings.${ps.name}`] = ps.value; - - await User.update(user._id, { - $set: x - }); + await Users.createQueryBuilder().update() + .set({ + clientData: { + [ps.name]: ps.value + }, + }) + .where('id = :id', { id: user.id }) + .execute(); // Publish event - publishMainStream(user._id, 'clientSettingUpdated', { + publishMainStream(user.id, 'clientSettingUpdated', { key: ps.name, value: ps.value }); - - return; }); diff --git a/src/server/api/endpoints/i/update-email.ts b/src/server/api/endpoints/i/update-email.ts index c90462d850..253017535f 100644 --- a/src/server/api/endpoints/i/update-email.ts +++ b/src/server/api/endpoints/i/update-email.ts @@ -1,5 +1,4 @@ import $ from 'cafy'; -import User, { pack } from '../../../../models/user'; import { publishMainStream } from '../../../../services/stream'; import define from '../../define'; import * as nodemailer from 'nodemailer'; @@ -9,6 +8,7 @@ import config from '../../../../config'; import * as ms from 'ms'; import * as bcrypt from 'bcryptjs'; import { apiLogger } from '../../logger'; +import { Users } from '../../../../models'; export const meta = { requireCredential: true, @@ -39,29 +39,25 @@ export default define(meta, async (ps, user) => { throw new Error('incorrect password'); } - await User.update(user._id, { - $set: { - email: ps.email, - emailVerified: false, - emailVerifyCode: null - } + await Users.update(user.id, { + email: ps.email, + emailVerified: false, + emailVerifyCode: null }); - const iObj = await pack(user._id, user, { + const iObj = await Users.pack(user.id, user, { detail: true, includeSecrets: true }); // Publish meUpdated event - publishMainStream(user._id, 'meUpdated', iObj); + publishMainStream(user.id, 'meUpdated', iObj); if (ps.email != null) { const code = rndstr('a-z0-9', 16); - await User.update(user._id, { - $set: { - emailVerifyCode: code - } + await Users.update(user.id, { + emailVerifyCode: code }); const meta = await fetchMeta(); @@ -84,7 +80,7 @@ export default define(meta, async (ps, user) => { transporter.sendMail({ from: meta.email, to: ps.email, - subject: meta.name, + subject: meta.name || 'Misskey', text: `To verify email, please click this link: ${link}` }, (error, info) => { if (error) { diff --git a/src/server/api/endpoints/i/update-home.ts b/src/server/api/endpoints/i/update-home.ts deleted file mode 100644 index e2c319887f..0000000000 --- a/src/server/api/endpoints/i/update-home.ts +++ /dev/null @@ -1,33 +0,0 @@ -import $ from 'cafy'; -import User from '../../../../models/user'; -import { publishMainStream } from '../../../../services/stream'; -import define from '../../define'; - -export const meta = { - requireCredential: true, - - secure: true, - - params: { - home: { - validator: $.arr($.obj({ - name: $.str, - id: $.str, - place: $.str, - data: $.obj() - }).strict()) - } - } -}; - -export default define(meta, async (ps, user) => { - await User.update(user._id, { - $set: { - 'clientSettings.home': ps.home - } - }); - - publishMainStream(user._id, 'homeUpdated', ps.home); - - return; -}); diff --git a/src/server/api/endpoints/i/update-mobile-home.ts b/src/server/api/endpoints/i/update-mobile-home.ts deleted file mode 100644 index 642e2b3e09..0000000000 --- a/src/server/api/endpoints/i/update-mobile-home.ts +++ /dev/null @@ -1,32 +0,0 @@ -import $ from 'cafy'; -import User from '../../../../models/user'; -import { publishMainStream } from '../../../../services/stream'; -import define from '../../define'; - -export const meta = { - requireCredential: true, - - secure: true, - - params: { - home: { - validator: $.arr($.obj({ - name: $.str, - id: $.str, - data: $.obj() - }).strict()) - } - } -}; - -export default define(meta, async (ps, user) => { - await User.update(user._id, { - $set: { - 'clientSettings.mobileHome': ps.home - } - }); - - publishMainStream(user._id, 'mobileHomeUpdated', ps.home); - - return; -}); diff --git a/src/server/api/endpoints/i/update-widget.ts b/src/server/api/endpoints/i/update-widget.ts deleted file mode 100644 index 67d342278d..0000000000 --- a/src/server/api/endpoints/i/update-widget.ts +++ /dev/null @@ -1,88 +0,0 @@ -import $ from 'cafy'; -import User from '../../../../models/user'; -import { publishMainStream } from '../../../../services/stream'; -import define from '../../define'; - -export const meta = { - requireCredential: true, - - secure: true, - - params: { - id: { - validator: $.str - }, - - data: { - validator: $.obj() - } - } -}; - -export default define(meta, async (ps, user) => { - if (ps.id == null && ps.data == null) throw new Error('you need to set id and data params if home param unset'); - - let widget; - - //#region Desktop home - if (widget == null && user.clientSettings.home) { - const desktopHome = user.clientSettings.home; - widget = desktopHome.find((w: any) => w.id == ps.id); - if (widget) { - widget.data = ps.data; - - await User.update(user._id, { - $set: { - 'clientSettings.home': desktopHome - } - }); - } - } - //#endregion - - //#region Mobile home - if (widget == null && user.clientSettings.mobileHome) { - const mobileHome = user.clientSettings.mobileHome; - widget = mobileHome.find((w: any) => w.id == ps.id); - if (widget) { - widget.data = ps.data; - - await User.update(user._id, { - $set: { - 'clientSettings.mobileHome': mobileHome - } - }); - } - } - //#endregion - - //#region Deck - if (widget == null && user.clientSettings.deck && user.clientSettings.deck.columns) { - const deck = user.clientSettings.deck; - for (const c of deck.columns.filter((c: any) => c.type == 'widgets')) { - for (const w of c.widgets.filter((w: any) => w.id == ps.id)) { - widget = w; - } - } - if (widget) { - widget.data = ps.data; - - await User.update(user._id, { - $set: { - 'clientSettings.deck': deck - } - }); - } - } - //#endregion - - if (widget) { - publishMainStream(user._id, 'widgetUpdated', { - id: ps.id, data: ps.data - }); - - return; - } else { - throw new Error('widget not found'); - } -}); diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 099ef33990..f3e5d41021 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -1,18 +1,16 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import User, { isValidName, isValidDescription, isValidLocation, isValidBirthday, pack } from '../../../../models/user'; +import { ID } from '../../../../misc/cafy-id'; import { publishMainStream } from '../../../../services/stream'; -import DriveFile from '../../../../models/drive-file'; import acceptAllFollowRequests from '../../../../services/following/requests/accept-all'; import { publishToFollowers } from '../../../../services/i/update'; import define from '../../define'; -import getDriveFileUrl from '../../../../misc/get-drive-file-url'; import { parse, parsePlain } from '../../../../mfm/parse'; import extractEmojis from '../../../../misc/extract-emojis'; import extractHashtags from '../../../../misc/extract-hashtags'; import * as langmap from 'langmap'; import { updateHashtag } from '../../../../services/update-hashtag'; import { ApiError } from '../../error'; +import { Users, DriveFiles } from '../../../../models'; export const meta = { desc: { @@ -24,18 +22,18 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { name: { - validator: $.optional.nullable.str.pipe(isValidName), + validator: $.optional.nullable.str.pipe(Users.isValidName), desc: { 'ja-JP': '名前(ハンドルネームやニックネーム)' } }, description: { - validator: $.optional.nullable.str.pipe(isValidDescription), + validator: $.optional.nullable.str.pipe(Users.isValidDescription), desc: { 'ja-JP': 'アカウントの説明や自己紹介' } @@ -49,14 +47,14 @@ export const meta = { }, location: { - validator: $.optional.nullable.str.pipe(isValidLocation), + validator: $.optional.nullable.str.pipe(Users.isValidLocation), desc: { 'ja-JP': '住んでいる地域、所在' } }, birthday: { - validator: $.optional.nullable.str.pipe(isValidBirthday), + validator: $.optional.nullable.str.pipe(Users.isValidBirthday), desc: { 'ja-JP': '誕生日 (YYYY-MM-DD形式)' } @@ -64,7 +62,6 @@ export const meta = { avatarId: { validator: $.optional.nullable.type(ID), - transform: transform, desc: { 'ja-JP': 'アイコンに設定する画像のドライブファイルID' } @@ -72,20 +69,11 @@ export const meta = { bannerId: { validator: $.optional.nullable.type(ID), - transform: transform, desc: { 'ja-JP': 'バナーに設定する画像のドライブファイルID' } }, - wallpaperId: { - validator: $.optional.nullable.type(ID), - transform: transform, - desc: { - 'ja-JP': '壁紙に設定する画像のドライブファイルID' - } - }, - isLocked: { validator: $.optional.bool, desc: { @@ -171,116 +159,76 @@ export default define(meta, async (ps, user, app) => { if (ps.name !== undefined) updates.name = ps.name; if (ps.description !== undefined) updates.description = ps.description; if (ps.lang !== undefined) updates.lang = ps.lang; - if (ps.location !== undefined) updates['profile.location'] = ps.location; - if (ps.birthday !== undefined) updates['profile.birthday'] = ps.birthday; + if (ps.location !== undefined) updates.location = ps.location; + if (ps.birthday !== undefined) updates.birthday = ps.birthday; if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId; if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId; - if (ps.wallpaperId !== undefined) updates.wallpaperId = ps.wallpaperId; if (typeof ps.isLocked == 'boolean') updates.isLocked = ps.isLocked; if (typeof ps.isBot == 'boolean') updates.isBot = ps.isBot; if (typeof ps.carefulBot == 'boolean') updates.carefulBot = ps.carefulBot; if (typeof ps.autoAcceptFollowed == 'boolean') updates.autoAcceptFollowed = ps.autoAcceptFollowed; if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat; - if (typeof ps.autoWatch == 'boolean') updates['settings.autoWatch'] = ps.autoWatch; - if (typeof ps.alwaysMarkNsfw == 'boolean') updates['settings.alwaysMarkNsfw'] = ps.alwaysMarkNsfw; + if (typeof ps.autoWatch == 'boolean') updates.autoWatch = ps.autoWatch; + if (typeof ps.alwaysMarkNsfw == 'boolean') updates.alwaysMarkNsfw = ps.alwaysMarkNsfw; if (ps.avatarId) { - const avatar = await DriveFile.findOne({ - _id: ps.avatarId - }); + const avatar = await DriveFiles.findOne(ps.avatarId); - if (avatar == null) throw new ApiError(meta.errors.noSuchAvatar); - if (!avatar.contentType.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage); + if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar); + if (!avatar.type.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage); - if (avatar.metadata.deletedAt) { - updates.avatarUrl = null; - } else { - updates.avatarUrl = getDriveFileUrl(avatar, true); + updates.avatarUrl = avatar.thumbnailUrl; - if (avatar.metadata.properties.avgColor) { - updates.avatarColor = avatar.metadata.properties.avgColor; - } + if (avatar.properties.avgColor) { + updates.avatarColor = avatar.properties.avgColor; } } if (ps.bannerId) { - const banner = await DriveFile.findOne({ - _id: ps.bannerId - }); - - if (banner == null) throw new ApiError(meta.errors.noSuchBanner); - if (!banner.contentType.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage); - - if (banner.metadata.deletedAt) { - updates.bannerUrl = null; - } else { - updates.bannerUrl = getDriveFileUrl(banner, false); - - if (banner.metadata.properties.avgColor) { - updates.bannerColor = banner.metadata.properties.avgColor; - } - } - } - - if (ps.wallpaperId !== undefined) { - if (ps.wallpaperId === null) { - updates.wallpaperUrl = null; - updates.wallpaperColor = null; - } else { - const wallpaper = await DriveFile.findOne({ - _id: ps.wallpaperId - }); + const banner = await DriveFiles.findOne(ps.bannerId); - if (wallpaper == null) throw new Error('wallpaper not found'); + if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner); + if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage); - if (wallpaper.metadata.deletedAt) { - updates.wallpaperUrl = null; - } else { - updates.wallpaperUrl = getDriveFileUrl(wallpaper); + updates.bannerUrl = banner.webpublicUrl; - if (wallpaper.metadata.properties.avgColor) { - updates.wallpaperColor = wallpaper.metadata.properties.avgColor; - } - } + if (banner.properties.avgColor) { + updates.bannerColor = banner.properties.avgColor; } } //#region emojis/tags - if (updates.name != null || updates.description != null) { - let emojis = [] as string[]; - let tags = [] as string[]; + let emojis = [] as string[]; + let tags = [] as string[]; - if (updates.name != null) { - const tokens = parsePlain(updates.name); - emojis = emojis.concat(extractEmojis(tokens)); - } + if (updates.name != null) { + const tokens = parsePlain(updates.name); + emojis = emojis.concat(extractEmojis(tokens)); + } - if (updates.description != null) { - const tokens = parse(updates.description); - emojis = emojis.concat(extractEmojis(tokens)); - tags = extractHashtags(tokens).map(tag => tag.toLowerCase()); - } + if (updates.description != null) { + const tokens = parse(updates.description); + emojis = emojis.concat(extractEmojis(tokens)); + tags = extractHashtags(tokens).map(tag => tag.toLowerCase()); + } - updates.emojis = emojis; - updates.tags = tags; + updates.emojis = emojis; + updates.tags = tags; - // ハッシュタグ更新 - for (const tag of tags) updateHashtag(user, tag, true, true); - for (const tag of (user.tags || []).filter(x => !tags.includes(x))) updateHashtag(user, tag, true, false); - } + // ハッシュタグ更新 + for (const tag of tags) updateHashtag(user, tag, true, true); + for (const tag of user.tags.filter(x => !tags.includes(x))) updateHashtag(user, tag, true, false); //#endregion - await User.update(user._id, { - $set: updates - }); + await Users.update(user.id, updates); - const iObj = await pack(user._id, user, { + const iObj = await Users.pack(user.id, user, { detail: true, includeSecrets: isSecure }); // Publish meUpdated event - publishMainStream(user._id, 'meUpdated', iObj); + publishMainStream(user.id, 'meUpdated', iObj); // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認 if (user.isLocked && ps.isLocked === false) { @@ -288,7 +236,7 @@ export default define(meta, async (ps, user, app) => { } // フォロワーにUpdateを配信 - publishToFollowers(user._id); + publishToFollowers(user.id); return iObj; }); diff --git a/src/server/api/endpoints/messaging/history.ts b/src/server/api/endpoints/messaging/history.ts index 699dc7c253..c0aec61212 100644 --- a/src/server/api/endpoints/messaging/history.ts +++ b/src/server/api/endpoints/messaging/history.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import Mute from '../../../../models/mute'; -import Message, { pack, IMessagingMessage } from '../../../../models/messaging-message'; import define from '../../define'; +import { MessagingMessage } from '../../../../models/entities/messaging-message'; +import { MessagingMessages, Mutings } from '../../../../models'; +import { Brackets } from 'typeorm'; export const meta = { desc: { @@ -31,34 +32,33 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const mute = await Mute.find({ - muterId: user._id, - deletedAt: { $exists: false } + const mute = await Mutings.find({ + muterId: user.id, }); - const history: IMessagingMessage[] = []; + const history: MessagingMessage[] = []; for (let i = 0; i < ps.limit; i++) { - const found = history.map(m => m.userId.equals(user._id) ? m.recipientId : m.userId); + const found = history.map(m => (m.userId === user.id) ? m.recipientId : m.userId); - const message = await Message.findOne({ - $or: [{ - userId: user._id - }, { - recipientId: user._id - }], - $and: [{ - userId: { $nin: found }, - recipientId: { $nin: found } - }, { - userId: { $nin: mute.map(m => m.muteeId) }, - recipientId: { $nin: mute.map(m => m.muteeId) } - }] - }, { - sort: { - createdAt: -1 - } - }); + const query = MessagingMessages.createQueryBuilder('message') + .where(new Brackets(qb => { qb + .where(`message.userId = :userId`, { userId: user.id }) + .orWhere(`message.recipientId = :userId`, { userId: user.id }); + })) + .orderBy('message.createdAt', 'DESC'); + + if (found.length > 0) { + query.andWhere(`message.userId NOT IN (:...found)`, { found: found }); + query.andWhere(`message.recipientId NOT IN (:...found)`, { found: found }); + } + + if (mute.length > 0) { + query.andWhere(`message.userId NOT IN (:...mute)`, { mute: mute.map(m => m.muteeId) }); + query.andWhere(`message.recipientId NOT IN (:...mute)`, { mute: mute.map(m => m.muteeId) }); + } + + const message = await query.getOne(); if (message) { history.push(message); @@ -67,5 +67,5 @@ export default define(meta, async (ps, user) => { } } - return await Promise.all(history.map(h => pack(h._id, user))); + return await Promise.all(history.map(h => MessagingMessages.pack(h.id, user))); }); diff --git a/src/server/api/endpoints/messaging/messages.ts b/src/server/api/endpoints/messaging/messages.ts index c19db45f1f..02c57b8d03 100644 --- a/src/server/api/endpoints/messaging/messages.ts +++ b/src/server/api/endpoints/messaging/messages.ts @@ -1,11 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Message from '../../../../models/messaging-message'; -import { pack } from '../../../../models/messaging-message'; +import { ID } from '../../../../misc/cafy-id'; import read from '../../common/read-messaging-message'; import define from '../../define'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { MessagingMessages } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { desc: { @@ -22,7 +22,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -36,12 +35,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, markAsRead: { @@ -73,43 +70,17 @@ export default define(meta, async (ps, user) => { throw e; }); - const query = { - $or: [{ - userId: user._id, - recipientId: recipient._id - }, { - userId: recipient._id, - recipientId: user._id - }] - } as any; + const query = makePaginationQuery(MessagingMessages.createQueryBuilder('message'), ps.sinceId, ps.untilId) + .andWhere(`(message.userId = :meId AND message.recipientId = :recipientId) OR (message.userId = :recipientId AND message.recipientId = :meId)`, { meId: user.id, recipientId: recipient.id }); - const sort = { - _id: -1 - }; - - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } - - const messages = await Message - .find(query, { - limit: ps.limit, - sort: sort - }); + const messages = await query.getMany(); // Mark all as read if (ps.markAsRead) { - read(user._id, recipient._id, messages); + read(user.id, recipient.id, messages.map(x => x.id)); } - return await Promise.all(messages.map(message => pack(message, user, { + return await Promise.all(messages.map(message => MessagingMessages.pack(message, user, { populateRecipient: false }))); }); diff --git a/src/server/api/endpoints/messaging/messages/create.ts b/src/server/api/endpoints/messaging/messages/create.ts index fc048e6edd..2c7e5ad2d9 100644 --- a/src/server/api/endpoints/messaging/messages/create.ts +++ b/src/server/api/endpoints/messaging/messages/create.ts @@ -1,17 +1,14 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import Message from '../../../../../models/messaging-message'; -import { isValidText } from '../../../../../models/messaging-message'; -import User from '../../../../../models/user'; -import Mute from '../../../../../models/mute'; -import DriveFile from '../../../../../models/drive-file'; -import { pack } from '../../../../../models/messaging-message'; +import { ID } from '../../../../../misc/cafy-id'; import { publishMainStream } from '../../../../../services/stream'; import { publishMessagingStream, publishMessagingIndexStream } from '../../../../../services/stream'; import pushSw from '../../../../../services/push-notification'; import define from '../../../define'; import { ApiError } from '../../../error'; import { getUser } from '../../../common/getters'; +import { MessagingMessages, DriveFiles, Mutings } from '../../../../../models'; +import { MessagingMessage } from '../../../../../models/entities/messaging-message'; +import { genId } from '../../../../../misc/gen-id'; export const meta = { desc: { @@ -28,7 +25,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -36,12 +32,11 @@ export const meta = { }, text: { - validator: $.optional.str.pipe(isValidText) + validator: $.optional.str.pipe(MessagingMessages.isValidText) }, fileId: { validator: $.optional.type(ID), - transform: transform, } }, @@ -78,7 +73,7 @@ export const meta = { export default define(meta, async (ps, user) => { // Myself - if (ps.userId.equals(user._id)) { + if (ps.userId === user.id) { throw new ApiError(meta.errors.recipientIsYourself); } @@ -90,12 +85,12 @@ export default define(meta, async (ps, user) => { let file = null; if (ps.fileId != null) { - file = await DriveFile.findOne({ - _id: ps.fileId, - 'metadata.userId': user._id + file = await DriveFiles.findOne({ + id: ps.fileId, + userId: user.id }); - if (file === null) { + if (file == null) { throw new ApiError(meta.errors.noSuchFile); } } @@ -105,16 +100,17 @@ export default define(meta, async (ps, user) => { throw new ApiError(meta.errors.contentRequired); } - const message = await Message.insert({ + const message = await MessagingMessages.save({ + id: genId(), createdAt: new Date(), - fileId: file ? file._id : undefined, - recipientId: recipient._id, - text: ps.text ? ps.text.trim() : undefined, - userId: user._id, + fileId: file ? file.id : null, + recipientId: recipient.id, + text: ps.text ? ps.text.trim() : null, + userId: user.id, isRead: false - }); + } as MessagingMessage); - const messageObj = await pack(message); + const messageObj = await MessagingMessages.pack(message); // 自分のストリーム publishMessagingStream(message.userId, message.recipientId, 'message', messageObj); @@ -126,25 +122,17 @@ export default define(meta, async (ps, user) => { publishMessagingIndexStream(message.recipientId, 'message', messageObj); publishMainStream(message.recipientId, 'messagingMessage', messageObj); - // Update flag - User.update({ _id: recipient._id }, { - $set: { - hasUnreadMessagingMessage: true - } - }); - // 2秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する setTimeout(async () => { - const freshMessage = await Message.findOne({ _id: message._id }, { isRead: true }); + const freshMessage = await MessagingMessages.findOne({ id: message.id }); if (freshMessage == null) return; // メッセージが削除されている場合もある if (!freshMessage.isRead) { //#region ただしミュートされているなら発行しない - const mute = await Mute.find({ - muterId: recipient._id, - deletedAt: { $exists: false } + const mute = await Mutings.find({ + muterId: recipient.id, }); const mutedUserIds = mute.map(m => m.muteeId.toString()); - if (mutedUserIds.indexOf(user._id.toString()) != -1) { + if (mutedUserIds.indexOf(user.id) != -1) { return; } //#endregion diff --git a/src/server/api/endpoints/messaging/messages/delete.ts b/src/server/api/endpoints/messaging/messages/delete.ts index 0ca12846c1..9f55caba62 100644 --- a/src/server/api/endpoints/messaging/messages/delete.ts +++ b/src/server/api/endpoints/messaging/messages/delete.ts @@ -1,10 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import Message from '../../../../../models/messaging-message'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { publishMessagingStream } from '../../../../../services/stream'; import * as ms from 'ms'; import { ApiError } from '../../../error'; +import { MessagingMessages } from '../../../../../models'; export const meta = { stability: 'stable', @@ -29,7 +29,6 @@ export const meta = { params: { messageId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のメッセージのID', 'en-US': 'Target message ID.' @@ -47,19 +46,17 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const message = await Message.findOne({ - _id: ps.messageId, - userId: user._id + const message = await MessagingMessages.findOne({ + id: ps.messageId, + userId: user.id }); - if (message === null) { + if (message == null) { throw new ApiError(meta.errors.noSuchMessage); } - await Message.remove({ _id: message._id }); + await MessagingMessages.delete(message.id); - publishMessagingStream(message.userId, message.recipientId, 'deleted', message._id); - publishMessagingStream(message.recipientId, message.userId, 'deleted', message._id); - - return; + publishMessagingStream(message.userId, message.recipientId, 'deleted', message.id); + publishMessagingStream(message.recipientId, message.userId, 'deleted', message.id); }); diff --git a/src/server/api/endpoints/messaging/messages/read.ts b/src/server/api/endpoints/messaging/messages/read.ts index aa8ecdc4ff..24a28285bf 100644 --- a/src/server/api/endpoints/messaging/messages/read.ts +++ b/src/server/api/endpoints/messaging/messages/read.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import Message from '../../../../../models/messaging-message'; +import { ID } from '../../../../../misc/cafy-id'; import read from '../../../common/read-messaging-message'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { MessagingMessages } from '../../../../../models'; export const meta = { desc: { @@ -20,7 +20,6 @@ export const meta = { params: { messageId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '既読にするメッセージのID', 'en-US': 'The ID of a message that you want to mark as read' @@ -38,16 +37,14 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const message = await Message.findOne({ - _id: ps.messageId, - recipientId: user._id + const message = await MessagingMessages.findOne({ + id: ps.messageId, + recipientId: user.id }); if (message == null) { throw new ApiError(meta.errors.noSuchMessage); } - read(user._id, message.userId, message); - - return; + read(user.id, message.userId, [message.id]); }); diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index a297f47e0e..785f21f22b 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -1,10 +1,10 @@ import $ from 'cafy'; import * as os from 'os'; import config from '../../../config'; -import Emoji from '../../../models/emoji'; import define from '../define'; import fetchMeta from '../../../misc/fetch-meta'; import * as pkg from '../../../../package.json'; +import { Emojis } from '../../../models'; export const meta = { stability: 'stable', @@ -81,14 +81,11 @@ export const meta = { export default define(meta, async (ps, me) => { const instance = await fetchMeta(); - const emojis = await Emoji.find({ host: null }, { - fields: { - _id: false - } - }); + const emojis = await Emojis.find({ host: null }); const response: any = { - maintainer: instance.maintainer, + maintainerName: instance.maintainerName, + maintainerEmail: instance.maintainerEmail, version: pkg.version, @@ -145,17 +142,12 @@ export default define(meta, async (ps, me) => { github: instance.enableGithubIntegration, discord: instance.enableDiscordIntegration, serviceWorker: instance.enableServiceWorker, - userRecommendation: { - external: instance.enableExternalUserRecommendation, - engine: instance.externalUserRecommendationEngine, - timeout: instance.externalUserRecommendationTimeout - } }; } if (me && (me.isAdmin || me.isModerator)) { response.useStarForReactionFallback = instance.useStarForReactionFallback; - response.hidedTags = instance.hidedTags; + response.hiddenTags = instance.hiddenTags; response.recaptchaSecretKey = instance.recaptchaSecretKey; response.proxyAccount = instance.proxyAccount; response.twitterConsumerKey = instance.twitterConsumerKey; @@ -164,9 +156,6 @@ export default define(meta, async (ps, me) => { response.githubClientSecret = instance.githubClientSecret; response.discordClientId = instance.discordClientId; response.discordClientSecret = instance.discordClientSecret; - response.enableExternalUserRecommendation = instance.enableExternalUserRecommendation; - response.externalUserRecommendationEngine = instance.externalUserRecommendationEngine; - response.externalUserRecommendationTimeout = instance.externalUserRecommendationTimeout; response.summalyProxy = instance.summalyProxy; response.email = instance.email; response.smtpSecure = instance.smtpSecure; diff --git a/src/server/api/endpoints/mute/create.ts b/src/server/api/endpoints/mute/create.ts index 7eaee90a05..d13c546fdc 100644 --- a/src/server/api/endpoints/mute/create.ts +++ b/src/server/api/endpoints/mute/create.ts @@ -1,9 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Mute from '../../../../models/mute'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { genId } from '../../../../misc/gen-id'; +import { Mutings, NoteWatchings } from '../../../../models'; +import { Muting } from '../../../../models/entities/muting'; export const meta = { desc: { @@ -15,12 +17,11 @@ export const meta = { requireCredential: true, - kind: 'account/write', + kind: 'write:mutes', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -53,7 +54,7 @@ export default define(meta, async (ps, user) => { const muter = user; // 自分自身 - if (user._id.equals(ps.userId)) { + if (user.id === ps.userId) { throw new ApiError(meta.errors.muteeIsYourself); } @@ -64,21 +65,25 @@ export default define(meta, async (ps, user) => { }); // Check if already muting - const exist = await Mute.findOne({ - muterId: muter._id, - muteeId: mutee._id + const exist = await Mutings.findOne({ + muterId: muter.id, + muteeId: mutee.id }); - if (exist !== null) { + if (exist != null) { throw new ApiError(meta.errors.alreadyMuting); } // Create mute - await Mute.insert({ + await Mutings.save({ + id: genId(), createdAt: new Date(), - muterId: muter._id, - muteeId: mutee._id, - }); + muterId: muter.id, + muteeId: mutee.id, + } as Muting); - return; + NoteWatchings.delete({ + userId: muter.id, + noteUserId: mutee.id + }); }); diff --git a/src/server/api/endpoints/mute/delete.ts b/src/server/api/endpoints/mute/delete.ts index 1a03f6371b..1aae15af91 100644 --- a/src/server/api/endpoints/mute/delete.ts +++ b/src/server/api/endpoints/mute/delete.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Mute from '../../../../models/mute'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { Mutings } from '../../../../models'; export const meta = { desc: { @@ -15,12 +15,11 @@ export const meta = { requireCredential: true, - kind: 'account/write', + kind: 'write:mutes', params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -53,7 +52,7 @@ export default define(meta, async (ps, user) => { const muter = user; // Check if the mutee is yourself - if (user._id.equals(ps.userId)) { + if (user.id === ps.userId) { throw new ApiError(meta.errors.muteeIsYourself); } @@ -64,19 +63,17 @@ export default define(meta, async (ps, user) => { }); // Check not muting - const exist = await Mute.findOne({ - muterId: muter._id, - muteeId: mutee._id + const exist = await Mutings.findOne({ + muterId: muter.id, + muteeId: mutee.id }); - if (exist === null) { + if (exist == null) { throw new ApiError(meta.errors.notMuting); } // Delete mute - await Mute.remove({ - _id: exist._id + await Mutings.delete({ + id: exist.id }); - - return; }); diff --git a/src/server/api/endpoints/mute/list.ts b/src/server/api/endpoints/mute/list.ts index 1b8f759496..5f2d184472 100644 --- a/src/server/api/endpoints/mute/list.ts +++ b/src/server/api/endpoints/mute/list.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Mute, { packMany } from '../../../../models/mute'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { Mutings } from '../../../../models'; export const meta = { desc: { @@ -13,7 +14,7 @@ export const meta = { requireCredential: true, - kind: 'account/read', + kind: 'read:mutes', params: { limit: { @@ -23,12 +24,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, }, @@ -41,30 +40,12 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const query = { - muterId: me._id - } as any; + const query = makePaginationQuery(Mutings.createQueryBuilder('muting'), ps.sinceId, ps.untilId) + .andWhere(`muting.muterId = :meId`, { meId: me.id }); - const sort = { - _id: -1 - }; + const mutings = await query + .take(ps.limit) + .getMany(); - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } - - const mutes = await Mute - .find(query, { - limit: ps.limit, - sort: sort - }); - - return await packMany(mutes, me); + return await Mutings.packMany(mutings, me); }); diff --git a/src/server/api/endpoints/my/apps.ts b/src/server/api/endpoints/my/apps.ts index 1a936c918b..d205d1674c 100644 --- a/src/server/api/endpoints/my/apps.ts +++ b/src/server/api/endpoints/my/apps.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; -import App, { pack } from '../../../../models/app'; import define from '../../define'; +import { Apps } from '../../../../models'; export const meta = { tags: ['account', 'app'], @@ -27,19 +27,16 @@ export const meta = { export default define(meta, async (ps, user) => { const query = { - userId: user._id + userId: user.id }; - const apps = await App - .find(query, { - limit: ps.limit, - skip: ps.offset, - sort: { - _id: -1 - } - }); + const apps = await Apps.find({ + where: query, + take: ps.limit, + skip: ps.offset, + }); - return await Promise.all(apps.map(app => pack(app, user, { + return await Promise.all(apps.map(app => Apps.pack(app, user, { detail: true }))); }); diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts index 835c515cfe..10f6e39845 100644 --- a/src/server/api/endpoints/notes.ts +++ b/src/server/api/endpoints/notes.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../misc/cafy-id'; -import Note, { packMany } from '../../../models/note'; +import { ID } from '../../../misc/cafy-id'; import define from '../define'; +import { makePaginationQuery } from '../common/make-pagination-query'; +import { Notes } from '../../../models'; export const meta = { desc: { @@ -39,14 +40,6 @@ export const meta = { } }, - media: { - validator: $.optional.bool, - deprecated: true, - desc: { - 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' - } - }, - poll: { validator: $.optional.bool, desc: { @@ -61,12 +54,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, }, @@ -79,43 +70,29 @@ export const meta = { }; export default define(meta, async (ps) => { - const sort = { - _id: -1 - }; - const query = { - deletedAt: null, - visibility: 'public', - localOnly: { $ne: true }, - } as any; - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } + const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere(`note.visibility = 'public'`) + .andWhere(`note.localOnly = FALSE`) + .leftJoinAndSelect('note.user', 'user'); if (ps.local) { - query['_user.host'] = null; + query.andWhere('note.userHost IS NULL'); } if (ps.reply != undefined) { - query.replyId = ps.reply ? { $exists: true, $ne: null } : null; + query.andWhere(ps.reply ? 'note.replyId IS NOT NULL' : 'note.replyId IS NULL'); } if (ps.renote != undefined) { - query.renoteId = ps.renote ? { $exists: true, $ne: null } : null; + query.andWhere(ps.renote ? 'note.renoteId IS NOT NULL' : 'note.renoteId IS NULL'); } - const withFiles = ps.withFiles != undefined ? ps.withFiles : ps.media; - - if (withFiles) query.fileIds = { $exists: true, $ne: null }; + if (ps.withFiles != undefined) { + query.andWhere(ps.withFiles ? `note.fileIds != '{}'` : `note.fileIds = '{}'`); + } if (ps.poll != undefined) { - query.poll = ps.poll ? { $exists: true, $ne: null } : null; + query.andWhere(ps.poll ? 'note.hasPoll = TRUE' : 'note.hasPoll = FALSE'); } // TODO @@ -123,10 +100,7 @@ export default define(meta, async (ps) => { // query.isBot = bot; //} - const notes = await Note.find(query, { - limit: ps.limit, - sort: sort - }); + const notes = await query.take(ps.limit).getMany(); - return await packMany(notes); + return await Notes.packMany(notes); }); diff --git a/src/server/api/endpoints/notes/children.ts b/src/server/api/endpoints/notes/children.ts index 3738459b71..72f2c39d6a 100644 --- a/src/server/api/endpoints/notes/children.ts +++ b/src/server/api/endpoints/notes/children.ts @@ -1,9 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note, { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import { getFriends } from '../../common/get-friends'; -import { getHideUserIds } from '../../common/get-hide-users'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { Brackets } from 'typeorm'; +import { Notes } from '../../../../models'; export const meta = { desc: { @@ -18,7 +20,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID' @@ -32,12 +33,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, }, @@ -50,83 +49,24 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const [followings, hideUserIds] = await Promise.all([ - // フォローを取得 - // Fetch following - user ? getFriends(user._id) : [], + const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere(new Brackets(qb => { qb + .where(`note.replyId = :noteId`, { noteId: ps.noteId }) + .orWhere(new Brackets(qb => { qb + .where(`note.renoteId = :noteId`, { noteId: ps.noteId }) + .andWhere(new Brackets(qb => { qb + .where(`note.text IS NOT NULL`) + .orWhere(`note.fileIds != '{}'`) + .orWhere(`note.hasPoll = TRUE`); + })); + })); + })) + .leftJoinAndSelect('note.user', 'user'); - // 隠すユーザーを取得 - getHideUserIds(user) - ]); + if (user) generateVisibilityQuery(query, user); + if (user) generateMuteQuery(query, user); - const visibleQuery = user == null ? [{ - visibility: { $in: [ 'public', 'home' ] } - }] : [{ - visibility: { $in: [ 'public', 'home' ] } - }, { - // myself (for followers/specified/private) - userId: user._id - }, { - // to me (for specified) - visibleUserIds: { $in: [ user._id ] } - }, { - visibility: 'followers', - $or: [{ - // フォロワーの投稿 - userId: { $in: followings.map(f => f.id) }, - }, { - // 自分の投稿へのリプライ - '_reply.userId': user._id, - }, { - // 自分へのメンションが含まれている - mentions: { $in: [ user._id ] } - }] - }]; + const notes = await query.take(ps.limit).getMany(); - const q = { - $and: [{ - $or: [{ - replyId: ps.noteId, - }, { - renoteId: ps.noteId, - $or: [{ - text: { $ne: null } - }, { - fileIds: { $ne: [] } - }, { - poll: { $ne: null } - }] - }] - }, { - $or: visibleQuery - }] - } as any; - - if (hideUserIds && hideUserIds.length > 0) { - q['userId'] = { - $nin: hideUserIds - }; - } - - const sort = { - _id: -1 - }; - - if (ps.sinceId) { - sort._id = 1; - q._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - q._id = { - $lt: ps.untilId - }; - } - - const notes = await Note.find(q, { - limit: ps.limit, - sort: sort - }); - - return await packMany(notes, user); + return await Notes.packMany(notes, user); }); diff --git a/src/server/api/endpoints/notes/conversation.ts b/src/server/api/endpoints/notes/conversation.ts index 702d8dc430..6defd79042 100644 --- a/src/server/api/endpoints/notes/conversation.ts +++ b/src/server/api/endpoints/notes/conversation.ts @@ -1,9 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note, { packMany, INote } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { ApiError } from '../../error'; import { getNote } from '../../common/getters'; +import { Note } from '../../../../models/entities/note'; +import { Notes } from '../../../../models'; export const meta = { desc: { @@ -18,7 +19,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID' @@ -58,12 +58,12 @@ export default define(meta, async (ps, user) => { throw e; }); - const conversation: INote[] = []; + const conversation: Note[] = []; let i = 0; async function get(id: any) { i++; - const p = await Note.findOne({ _id: id }); + const p = await Notes.findOne(id); if (i > ps.offset) { conversation.push(p); @@ -82,5 +82,5 @@ export default define(meta, async (ps, user) => { await get(note.replyId); } - return await packMany(conversation, user); + return await Notes.packMany(conversation, user); }); diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 8cc5e4b815..138f05fb3b 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -1,14 +1,15 @@ import $ from 'cafy'; -import ID, { transform, transformMany } from '../../../../misc/cafy-id'; import * as ms from 'ms'; import { length } from 'stringz'; -import Note, { INote, isValidCw, pack } from '../../../../models/note'; -import User, { IUser } from '../../../../models/user'; -import DriveFile, { IDriveFile } from '../../../../models/drive-file'; import create from '../../../../services/note/create'; import define from '../../define'; import fetchMeta from '../../../../misc/fetch-meta'; import { ApiError } from '../../error'; +import { ID } from '../../../../misc/cafy-id'; +import { User } from '../../../../models/entities/user'; +import { Users, DriveFiles, Notes } from '../../../../models'; +import { DriveFile } from '../../../../models/entities/drive-file'; +import { Note } from '../../../../models/entities/note'; let maxNoteTextLength = 1000; @@ -34,7 +35,7 @@ export const meta = { max: 300 }, - kind: 'note-write', + kind: 'write:notes', params: { visibility: { @@ -47,7 +48,6 @@ export const meta = { visibleUserIds: { validator: $.optional.arr($.type(ID)).unique().min(0), - transform: transformMany, desc: { 'ja-JP': '(投稿の公開範囲が specified の場合)投稿を閲覧できるユーザー' } @@ -64,7 +64,7 @@ export const meta = { }, cw: { - validator: $.optional.nullable.str.pipe(isValidCw), + validator: $.optional.nullable.str.pipe(Notes.validateCw), desc: { 'ja-JP': 'コンテンツの警告。このパラメータを指定すると設定したテキストで投稿のコンテンツを隠す事が出来ます。' } @@ -129,7 +129,6 @@ export const meta = { fileIds: { validator: $.optional.arr($.type(ID)).unique().range(1, 4), - transform: transformMany, desc: { 'ja-JP': '添付するファイル' } @@ -137,7 +136,6 @@ export const meta = { mediaIds: { validator: $.optional.arr($.type(ID)).unique().range(1, 4), - transform: transformMany, deprecated: true, desc: { 'ja-JP': '添付するファイル (このパラメータは廃止予定です。代わりに fileIds を使ってください。)' @@ -146,7 +144,6 @@ export const meta = { replyId: { validator: $.optional.type(ID), - transform: transform, desc: { 'ja-JP': '返信対象' } @@ -154,7 +151,6 @@ export const meta = { renoteId: { validator: $.optional.type(ID), - transform: transform, desc: { 'ja-JP': 'Renote対象' } @@ -227,32 +223,28 @@ export const meta = { }; export default define(meta, async (ps, user, app) => { - let visibleUsers: IUser[] = []; + let visibleUsers: User[] = []; if (ps.visibleUserIds) { - visibleUsers = await Promise.all(ps.visibleUserIds.map(id => User.findOne({ - _id: id - }))); + visibleUsers = await Promise.all(ps.visibleUserIds.map(id => Users.findOne(id))); } - let files: IDriveFile[] = []; + let files: DriveFile[] = []; const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null; if (fileIds != null) { files = await Promise.all(fileIds.map(fileId => { - return DriveFile.findOne({ - _id: fileId, - 'metadata.userId': user._id + return DriveFiles.findOne({ + id: fileId, + userId: user.id }); })); files = files.filter(file => file != null); } - let renote: INote = null; + let renote: Note = null; if (ps.renoteId != null) { // Fetch renote to note - renote = await Note.findOne({ - _id: ps.renoteId - }); + renote = await Notes.findOne(ps.renoteId); if (renote == null) { throw new ApiError(meta.errors.noSuchRenoteTarget); @@ -261,14 +253,12 @@ export default define(meta, async (ps, user, app) => { } } - let reply: INote = null; + let reply: Note = null; if (ps.replyId != null) { // Fetch reply - reply = await Note.findOne({ - _id: ps.replyId - }); + reply = await Notes.findOne(ps.replyId); - if (reply === null) { + if (reply == null) { throw new ApiError(meta.errors.noSuchReplyTarget); } @@ -279,12 +269,6 @@ export default define(meta, async (ps, user, app) => { } if (ps.poll) { - (ps.poll as any).choices = (ps.poll as any).choices.map((choice: string, i: number) => ({ - id: i, // IDを付与 - text: choice.trim(), - votes: 0 - })); - if (typeof ps.poll.expiresAt === 'number') { if (ps.poll.expiresAt < Date.now()) throw new ApiError(meta.errors.cannotCreateAlreadyExpiredPoll); @@ -298,11 +282,6 @@ export default define(meta, async (ps, user, app) => { throw new ApiError(meta.errors.contentRequired); } - // 後方互換性のため - if (ps.visibility == 'private') { - ps.visibility = 'specified'; - } - // 投稿を作成 const note = await create(user, { createdAt: new Date(), @@ -311,7 +290,7 @@ export default define(meta, async (ps, user, app) => { choices: ps.poll.choices, multiple: ps.poll.multiple || false, expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null - } : undefined, + } : null, text: ps.text, reply, renote, @@ -321,13 +300,13 @@ export default define(meta, async (ps, user, app) => { localOnly: ps.localOnly, visibility: ps.visibility, visibleUsers, - apMentions: ps.noExtractMentions ? [] : undefined, - apHashtags: ps.noExtractHashtags ? [] : undefined, - apEmojis: ps.noExtractEmojis ? [] : undefined, + apMentions: ps.noExtractMentions ? [] : null, + apHashtags: ps.noExtractHashtags ? [] : null, + apEmojis: ps.noExtractEmojis ? [] : null, geo: ps.geo }); return { - createdNote: await pack(note, user) + createdNote: await Notes.pack(note, user) }; }); diff --git a/src/server/api/endpoints/notes/delete.ts b/src/server/api/endpoints/notes/delete.ts index 399f9288d6..dbaf91bca3 100644 --- a/src/server/api/endpoints/notes/delete.ts +++ b/src/server/api/endpoints/notes/delete.ts @@ -1,11 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import deleteNote from '../../../../services/note/delete'; -import User from '../../../../models/user'; import define from '../../define'; import * as ms from 'ms'; import { getNote } from '../../common/getters'; import { ApiError } from '../../error'; +import { Users } from '../../../../models'; export const meta = { stability: 'stable', @@ -19,7 +19,7 @@ export const meta = { requireCredential: true, - kind: 'note-write', + kind: 'write:notes', limit: { duration: ms('1hour'), @@ -30,7 +30,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' @@ -59,9 +58,10 @@ export default define(meta, async (ps, user) => { throw e; }); - if (!user.isAdmin && !user.isModerator && !note.userId.equals(user._id)) { + if (!user.isAdmin && !user.isModerator && (note.userId !== user.id)) { throw new ApiError(meta.errors.accessDenied); } - await deleteNote(await User.findOne({ _id: note.userId }), note); + // この操作を行うのが投稿者とは限らない(例えばモデレーター)ため + await deleteNote(await Users.findOne(note.userId), note); }); diff --git a/src/server/api/endpoints/notes/favorites/create.ts b/src/server/api/endpoints/notes/favorites/create.ts index 9cde1a7dcf..7e04637758 100644 --- a/src/server/api/endpoints/notes/favorites/create.ts +++ b/src/server/api/endpoints/notes/favorites/create.ts @@ -1,9 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import Favorite from '../../../../../models/favorite'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; import { getNote } from '../../../common/getters'; +import { NoteFavorites } from '../../../../../models'; +import { genId } from '../../../../../misc/gen-id'; export const meta = { stability: 'stable', @@ -22,7 +23,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' @@ -53,21 +53,20 @@ export default define(meta, async (ps, user) => { }); // if already favorited - const exist = await Favorite.findOne({ - noteId: note._id, - userId: user._id + const exist = await NoteFavorites.findOne({ + noteId: note.id, + userId: user.id }); - if (exist !== null) { + if (exist != null) { throw new ApiError(meta.errors.alreadyFavorited); } // Create favorite - await Favorite.insert({ + await NoteFavorites.save({ + id: genId(), createdAt: new Date(), - noteId: note._id, - userId: user._id + noteId: note.id, + userId: user.id }); - - return; }); diff --git a/src/server/api/endpoints/notes/favorites/delete.ts b/src/server/api/endpoints/notes/favorites/delete.ts index e2c787f3b5..a889c84d4d 100644 --- a/src/server/api/endpoints/notes/favorites/delete.ts +++ b/src/server/api/endpoints/notes/favorites/delete.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import Favorite from '../../../../../models/favorite'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; import { getNote } from '../../../common/getters'; +import { NoteFavorites } from '../../../../../models'; export const meta = { stability: 'stable', @@ -22,7 +22,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' @@ -53,19 +52,15 @@ export default define(meta, async (ps, user) => { }); // if already favorited - const exist = await Favorite.findOne({ - noteId: note._id, - userId: user._id + const exist = await NoteFavorites.findOne({ + noteId: note.id, + userId: user.id }); - if (exist === null) { + if (exist == null) { throw new ApiError(meta.errors.notFavorited); } // Delete favorite - await Favorite.remove({ - _id: exist._id - }); - - return; + await NoteFavorites.delete(exist.id); }); diff --git a/src/server/api/endpoints/notes/featured.ts b/src/server/api/endpoints/notes/featured.ts index 3648b307d7..c44a5275bb 100644 --- a/src/server/api/endpoints/notes/featured.ts +++ b/src/server/api/endpoints/notes/featured.ts @@ -1,8 +1,7 @@ import $ from 'cafy'; -import Note from '../../../../models/note'; -import { packMany } from '../../../../models/note'; import define from '../../define'; -import { getHideUserIds } from '../../common/get-hide-users'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { Notes } from '../../../../models'; export const meta = { desc: { @@ -35,25 +34,14 @@ export const meta = { export default define(meta, async (ps, user) => { const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで - const hideUserIds = await getHideUserIds(user); + const query = Notes.createQueryBuilder('note') + .andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) }) + .andWhere(`note.visibility = 'public'`) + .leftJoinAndSelect('note.user', 'user'); - const notes = await Note.find({ - createdAt: { - $gt: new Date(Date.now() - day) - }, - deletedAt: null, - visibility: 'public', - '_user.host': null, - ...(hideUserIds && hideUserIds.length > 0 ? { userId: { $nin: hideUserIds } } : {}) - }, { - limit: ps.limit, - sort: { - score: -1 - }, - hint: { - score: -1 - } - }); + if (user) generateMuteQuery(query, user); + + const notes = await query.orderBy('note.score', 'DESC').take(ps.limit).getMany(); - return await packMany(notes, user); + return await Notes.packMany(notes, user); }); diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts index 0eb761cdb6..7bf62f366b 100644 --- a/src/server/api/endpoints/notes/global-timeline.ts +++ b/src/server/api/endpoints/notes/global-timeline.ts @@ -1,11 +1,12 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note from '../../../../models/note'; -import { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import fetchMeta from '../../../../misc/fetch-meta'; -import { getHideUserIds } from '../../common/get-hide-users'; import { ApiError } from '../../error'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { Notes } from '../../../../models'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { activeUsersChart } from '../../../../services/chart'; export const meta = { desc: { @@ -22,14 +23,6 @@ export const meta = { } }, - mediaOnly: { - validator: $.optional.bool, - deprecated: true, - desc: { - 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' - } - }, - limit: { validator: $.optional.num.range(1, 100), default: 10 @@ -37,12 +30,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, sinceDate: { @@ -71,6 +62,7 @@ export const meta = { }; export default define(meta, async (ps, user) => { + // TODO どっかにキャッシュ const m = await fetchMeta(); if (m.disableGlobalTimeline) { if (user == null || (!user.isAdmin && !user.isModerator)) { @@ -78,68 +70,25 @@ export default define(meta, async (ps, user) => { } } - // 隠すユーザーを取得 - const hideUserIds = await getHideUserIds(user); - //#region Construct query - const sort = { - _id: -1 - }; - - const query = { - deletedAt: null, - - // public only - visibility: 'public', + const query = makePaginationQuery(Notes.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('note.visibility = \'public\'') + .andWhere('note.replyId IS NULL') + .leftJoinAndSelect('note.user', 'user'); - replyId: null - } as any; + if (user) generateMuteQuery(query, user); - if (hideUserIds && hideUserIds.length > 0) { - query.userId = { - $nin: hideUserIds - }; - - query['_reply.userId'] = { - $nin: hideUserIds - }; - - query['_renote.userId'] = { - $nin: hideUserIds - }; + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } + //#endregion - const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; + const timeline = await query.take(ps.limit).getMany(); - if (withFiles) { - query.fileIds = { $exists: true, $ne: [] }; + if (user) { + activeUsersChart.update(user); } - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } else if (ps.sinceDate) { - sort._id = 1; - query.createdAt = { - $gt: new Date(ps.sinceDate) - }; - } else if (ps.untilDate) { - query.createdAt = { - $lt: new Date(ps.untilDate) - }; - } - //#endregion - - const timeline = await Note.find(query, { - limit: ps.limit, - sort: sort - }); - - return await packMany(timeline, user); + return await Notes.packMany(timeline, user); }); diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index 57ef4c3e15..cd07341342 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -1,12 +1,14 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note from '../../../../models/note'; -import { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import fetchMeta from '../../../../misc/fetch-meta'; -import activeUsersChart from '../../../../services/chart/active-users'; -import { getHideUserIds } from '../../common/get-hide-users'; import { ApiError } from '../../error'; +import { Notes } from '../../../../models'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { activeUsersChart } from '../../../../services/chart'; +import { Brackets } from 'typeorm'; export const meta = { desc: { @@ -23,14 +25,6 @@ export const meta = { } }, - mediaOnly: { - validator: $.optional.bool, - deprecated: true, - desc: { - 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' - } - }, - fileType: { validator: $.optional.arr($.str), desc: { @@ -53,12 +47,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, sinceDate: { @@ -87,6 +79,7 @@ export const meta = { }; export default define(meta, async (ps, user) => { + // TODO どっかにキャッシュ const m = await fetchMeta(); if (m.disableLocalTimeline) { if (user == null || (!user.isAdmin && !user.isModerator)) { @@ -94,90 +87,44 @@ export default define(meta, async (ps, user) => { } } - // 隠すユーザーを取得 - const hideUserIds = await getHideUserIds(user); - //#region Construct query - const sort = { - _id: -1 - }; - - const query = { - deletedAt: null, - - // public only - visibility: 'public', - - // リプライでない - //replyId: null, + const query = makePaginationQuery(Notes.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)') + .leftJoinAndSelect('note.user', 'user'); - // local - '_user.host': null - } as any; + if (user) generateVisibilityQuery(query, user); + if (user) generateMuteQuery(query, user); - if (hideUserIds && hideUserIds.length > 0) { - query.userId = { - $nin: hideUserIds - }; - - query['_reply.userId'] = { - $nin: hideUserIds - }; - - query['_renote.userId'] = { - $nin: hideUserIds - }; - } - - const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; - - if (withFiles) { - query.fileIds = { $exists: true, $ne: [] }; + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } if (ps.fileType) { - query.fileIds = { $exists: true, $ne: [] }; - - query['_files.contentType'] = { - $in: ps.fileType - }; + query.andWhere('note.fileIds != \'{}\''); + query.andWhere(new Brackets(qb => { + for (const type of ps.fileType) { + const i = ps.fileType.indexOf(type); + qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type }); + } + })); if (ps.excludeNsfw) { - query['_files.metadata.isSensitive'] = { + // v11 TODO + /* + query['_files.isSensitive'] = { $ne: true }; + */ } } - - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } else if (ps.sinceDate) { - sort._id = 1; - query.createdAt = { - $gt: new Date(ps.sinceDate) - }; - } else if (ps.untilDate) { - query.createdAt = { - $lt: new Date(ps.untilDate) - }; - } //#endregion - const timeline = await Note.find(query, { - limit: ps.limit, - sort: sort - }); + const timeline = await query.take(ps.limit).getMany(); if (user) { activeUsersChart.update(user); } - return await packMany(timeline, user); + return await Notes.packMany(timeline, user); }); diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index 91333174ed..0bbe7d3327 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -1,11 +1,12 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note from '../../../../models/note'; -import { getFriendIds, getFriends } from '../../common/get-friends'; -import { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import read from '../../../../services/note/read'; -import { getHideUserIds } from '../../common/get-hide-users'; +import { Notes, Followings } from '../../../../models'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { Brackets } from 'typeorm'; export const meta = { desc: { @@ -30,12 +31,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, visibility: { @@ -52,97 +51,34 @@ export const meta = { }; export default define(meta, async (ps, user) => { - // フォローを取得 - const followings = await getFriends(user._id); + const followingQuery = Followings.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: user.id }); - const visibleQuery = [{ - visibility: { $in: [ 'public', 'home' ] } - }, { - // myself (for followers/specified/private) - userId: user._id - }, { - // to me (for specified) - visibleUserIds: { $in: [ user._id ] } - }, { - visibility: 'followers', - $or: [{ - // フォロワーの投稿 - userId: { $in: followings.map(f => f.id) }, - }, { - // 自分の投稿へのリプライ - '_reply.userId': user._id, - }, { - // 自分へのメンションが含まれている - mentions: { $in: [ user._id ] } - }] - }]; + const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere(new Brackets(qb => { qb + .where(`:meId = ANY(note.mentions)`, { meId: user.id }) + .orWhere(`:meId = ANY(note.visibleUserIds)`, { meId: user.id }); + })) + .leftJoinAndSelect('note.user', 'user'); - const query = { - $and: [{ - deletedAt: null, - }, { - $or: visibleQuery, - }], - - $or: [{ - mentions: user._id - }, { - visibleUserIds: user._id - }] - } as any; - - // 隠すユーザーを取得 - const hideUserIds = await getHideUserIds(user); - - if (hideUserIds && hideUserIds.length > 0) { - query.userId = { - $nin: hideUserIds - }; - - query['_reply.userId'] = { - $nin: hideUserIds - }; - - query['_renote.userId'] = { - $nin: hideUserIds - }; - } - - const sort = { - _id: -1 - }; + generateVisibilityQuery(query, user); + generateMuteQuery(query, user); if (ps.visibility) { - query.visibility = ps.visibility; + query.andWhere('note.visibility = :visibility', { visibility: ps.visibility }); } if (ps.following) { - const followingIds = await getFriendIds(user._id); - - query.userId = { - $in: followingIds - }; - } - - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; + query.andWhere(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: user.id }); + query.setParameters(followingQuery.getParameters()); } - const mentions = await Note.find(query, { - limit: ps.limit, - sort: sort - }); + const mentions = await query.take(ps.limit).getMany(); for (const note of mentions) { - read(user._id, note._id); + read(user.id, note.id); } - return await packMany(mentions, user); + return await Notes.packMany(mentions, user); }); diff --git a/src/server/api/endpoints/notes/polls/recommendation.ts b/src/server/api/endpoints/notes/polls/recommendation.ts index 9adabdf0e9..ff838d4f4f 100644 --- a/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/src/server/api/endpoints/notes/polls/recommendation.ts @@ -1,8 +1,7 @@ import $ from 'cafy'; -import Vote from '../../../../../models/poll-vote'; -import Note, { pack } from '../../../../../models/note'; import define from '../../../define'; -import { getHideUserIds } from '../../../common/get-hide-users'; +import { Polls, Mutings, Notes, PollVotes } from '../../../../../models'; +import { Brackets, In } from 'typeorm'; export const meta = { desc: { @@ -28,51 +27,46 @@ export const meta = { }; export default define(meta, async (ps, user) => { - // Get votes - const votes = await Vote.find({ - userId: user._id - }, { - fields: { - _id: false, - noteId: true - } - }); + const query = Polls.createQueryBuilder('poll') + .where('poll.userHost IS NULL') + .andWhere(`poll.userId != :meId`, { meId: user.id }) + .andWhere(`poll.noteVisibility = 'public'`) + .andWhere(new Brackets(qb => { qb + .where('poll.expiresAt IS NULL') + .orWhere('poll.expiresAt > :now', { now: new Date() }); + })); - const nin = votes && votes.length != 0 ? votes.map(v => v.noteId) : []; + //#region exclude arleady voted polls + const votedQuery = PollVotes.createQueryBuilder('vote') + .select('vote.noteId') + .where('vote.userId = :meId', { meId: user.id }); - // 隠すユーザーを取得 - const hideUserIds = await getHideUserIds(user); + query + .andWhere(`poll.noteId NOT IN (${ votedQuery.getQuery() })`); - const notes = await Note.find({ - '_user.host': null, - _id: { - $nin: nin - }, - userId: { - $ne: user._id, - $nin: hideUserIds - }, - visibility: 'public', - poll: { - $exists: true, - $ne: null - }, - $or: [{ - 'poll.expiresAt': null - }, { - 'poll.expiresAt': { - $gt: new Date() - } - }], - }, { - limit: ps.limit, - skip: ps.offset, - sort: { - _id: -1 - } + query.setParameters(votedQuery.getParameters()); + //#endregion + + //#region mute + const mutingQuery = Mutings.createQueryBuilder('muting') + .select('muting.muteeId') + .where('muting.muterId = :muterId', { muterId: user.id }); + + query + .andWhere(`poll.userId NOT IN (${ mutingQuery.getQuery() })`); + + query.setParameters(mutingQuery.getParameters()); + //#endregion + + const polls = await query.take(ps.limit).skip(ps.offset).getMany(); + + if (polls.length === 0) return []; + + const notes = await Notes.find({ + id: In(polls.map(poll => poll.noteId)) }); - return await Promise.all(notes.map(note => pack(note, user, { + return await Notes.packMany(notes, user, { detail: true - }))); + }); }); diff --git a/src/server/api/endpoints/notes/polls/vote.ts b/src/server/api/endpoints/notes/polls/vote.ts index ed20e0221f..7d0ed6e4f9 100644 --- a/src/server/api/endpoints/notes/polls/vote.ts +++ b/src/server/api/endpoints/notes/polls/vote.ts @@ -1,19 +1,19 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import Vote from '../../../../../models/poll-vote'; -import Note from '../../../../../models/note'; -import Watching from '../../../../../models/note-watching'; +import { ID } from '../../../../../misc/cafy-id'; import watch from '../../../../../services/note/watch'; import { publishNoteStream } from '../../../../../services/stream'; -import notify from '../../../../../services/create-notification'; +import { createNotification } from '../../../../../services/create-notification'; import define from '../../../define'; -import User, { IRemoteUser } from '../../../../../models/user'; import { ApiError } from '../../../error'; import { getNote } from '../../../common/getters'; import { deliver } from '../../../../../queue'; import { renderActivity } from '../../../../../remote/activitypub/renderer'; import renderVote from '../../../../../remote/activitypub/renderer/vote'; import { deliverQuestionUpdate } from '../../../../../services/note/polls/update'; +import { PollVotes, NoteWatchings, Users, Polls } from '../../../../../models'; +import { Not } from 'typeorm'; +import { IRemoteUser } from '../../../../../models/entities/user'; +import { genId } from '../../../../../misc/gen-id'; export const meta = { desc: { @@ -30,7 +30,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID' @@ -84,26 +83,28 @@ export default define(meta, async (ps, user) => { throw e; }); - if (note.poll == null) { + if (!note.hasPoll) { throw new ApiError(meta.errors.noPoll); } - if (note.poll.expiresAt && note.poll.expiresAt < createdAt) { + const poll = await Polls.findOne({ noteId: note.id }); + + if (poll.expiresAt && poll.expiresAt < createdAt) { throw new ApiError(meta.errors.alreadyExpired); } - if (!note.poll.choices.some(x => x.id == ps.choice)) { + if (poll.choices[ps.choice] == null) { throw new ApiError(meta.errors.invalidChoice); } // if already voted - const exist = await Vote.find({ - noteId: note._id, - userId: user._id + const exist = await PollVotes.find({ + noteId: note.id, + userId: user.id }); if (exist.length) { - if (note.poll.multiple) { + if (poll.multiple) { if (exist.some(x => x.choice == ps.choice)) throw new ApiError(meta.errors.alreadyVoted); } else { @@ -112,69 +113,54 @@ export default define(meta, async (ps, user) => { } // Create vote - const vote = await Vote.insert({ + const vote = await PollVotes.save({ + id: genId(), createdAt, - noteId: note._id, - userId: user._id, + noteId: note.id, + userId: user.id, choice: ps.choice }); - const inc: any = {}; - inc[`poll.choices.${note.poll.choices.findIndex(c => c.id == ps.choice)}.votes`] = 1; - // Increment votes count - await Note.update({ _id: note._id }, { - $inc: inc - }); + const index = ps.choice + 1; // In SQL, array index is 1 based + await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE id = '${poll.id}'`); - publishNoteStream(note._id, 'pollVoted', { + publishNoteStream(note.id, 'pollVoted', { choice: ps.choice, - userId: user._id.toHexString() + userId: user.id }); // Notify - notify(note.userId, user._id, 'poll_vote', { - noteId: note._id, + createNotification(note.userId, user.id, 'pollVote', { + noteId: note.id, choice: ps.choice }); // Fetch watchers - Watching - .find({ - noteId: note._id, - userId: { $ne: user._id }, - // 削除されたドキュメントは除く - deletedAt: { $exists: false } - }, { - fields: { - userId: true - } - }) - .then(watchers => { - for (const watcher of watchers) { - notify(watcher.userId, user._id, 'poll_vote', { - noteId: note._id, - choice: ps.choice - }); - } - }); + NoteWatchings.find({ + noteId: note.id, + userId: Not(user.id), + }).then(watchers => { + for (const watcher of watchers) { + createNotification(watcher.userId, user.id, 'pollVote', { + noteId: note.id, + choice: ps.choice + }); + } + }); // この投稿をWatchする - if (user.settings.autoWatch !== false) { - watch(user._id, note); + if (user.autoWatch !== false) { + watch(user.id, note); } // リモート投票の場合リプライ送信 - if (note._user.host != null) { - const pollOwner: IRemoteUser = await User.findOne({ - _id: note.userId - }); + if (note.userHost != null) { + const pollOwner: IRemoteUser = await Users.findOne(note.userId); - deliver(user, renderActivity(await renderVote(user, vote, note, pollOwner)), pollOwner.inbox); + deliver(user, renderActivity(await renderVote(user, vote, note, poll, pollOwner)), pollOwner.inbox); } // リモートフォロワーにUpdate配信 - deliverQuestionUpdate(note._id); - - return; + deliverQuestionUpdate(note.id); }); diff --git a/src/server/api/endpoints/notes/reactions.ts b/src/server/api/endpoints/notes/reactions.ts index 7d977154f2..b1b5ca9d33 100644 --- a/src/server/api/endpoints/notes/reactions.ts +++ b/src/server/api/endpoints/notes/reactions.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import NoteReaction, { pack } from '../../../../models/note-reaction'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { getNote } from '../../common/getters'; import { ApiError } from '../../error'; +import { NoteReactions } from '../../../../models'; export const meta = { desc: { @@ -18,7 +18,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'The ID of the target note' @@ -37,12 +36,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, }, }, @@ -69,29 +66,17 @@ export default define(meta, async (ps, user) => { }); const query = { - noteId: note._id - } as any; - - const sort = { - _id: -1 + noteId: note.id }; - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } - - const reactions = await NoteReaction.find(query, { - limit: ps.limit, + const reactions = await NoteReactions.find({ + where: query, + take: ps.limit, skip: ps.offset, - sort: sort + order: { + id: -1 + } }); - return await Promise.all(reactions.map(reaction => pack(reaction, user))); + return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, user))); }); diff --git a/src/server/api/endpoints/notes/reactions/create.ts b/src/server/api/endpoints/notes/reactions/create.ts index 299ed30278..b6aa4c58f3 100644 --- a/src/server/api/endpoints/notes/reactions/create.ts +++ b/src/server/api/endpoints/notes/reactions/create.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import createReaction from '../../../../../services/note/reaction/create'; import define from '../../../define'; import { getNote } from '../../../common/getters'; @@ -17,12 +17,11 @@ export const meta = { requireCredential: true, - kind: 'reaction-write', + kind: 'write:reactions', params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿' } diff --git a/src/server/api/endpoints/notes/reactions/delete.ts b/src/server/api/endpoints/notes/reactions/delete.ts index 08442226c5..0bdea58027 100644 --- a/src/server/api/endpoints/notes/reactions/delete.ts +++ b/src/server/api/endpoints/notes/reactions/delete.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import * as ms from 'ms'; import deleteReaction from '../../../../../services/note/reaction/delete'; @@ -16,7 +16,7 @@ export const meta = { requireCredential: true, - kind: 'reaction-write', + kind: 'write:reactions', limit: { duration: ms('1hour'), @@ -27,7 +27,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID' diff --git a/src/server/api/endpoints/notes/renotes.ts b/src/server/api/endpoints/notes/renotes.ts index 15dcf55dce..81b899836d 100644 --- a/src/server/api/endpoints/notes/renotes.ts +++ b/src/server/api/endpoints/notes/renotes.ts @@ -1,9 +1,12 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note, { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { getNote } from '../../common/getters'; import { ApiError } from '../../error'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { Notes } from '../../../../models'; export const meta = { desc: { @@ -18,7 +21,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID' @@ -32,12 +34,10 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, }, untilId: { validator: $.optional.type(ID), - transform: transform, } }, @@ -63,29 +63,14 @@ export default define(meta, async (ps, user) => { throw e; }); - const sort = { - _id: -1 - }; + const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere(`note.renoteId = :renoteId`, { renoteId: note.id }) + .leftJoinAndSelect('note.user', 'user'); - const query = { - renoteId: note._id - } as any; + if (user) generateVisibilityQuery(query, user); + if (user) generateMuteQuery(query, user); - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } - - const renotes = await Note.find(query, { - limit: ps.limit, - sort: sort - }); + const renotes = await query.take(ps.limit).getMany(); - return await packMany(renotes, user); + return await Notes.packMany(renotes, user); }); diff --git a/src/server/api/endpoints/notes/replies.ts b/src/server/api/endpoints/notes/replies.ts index c80fd73205..09b0f17164 100644 --- a/src/server/api/endpoints/notes/replies.ts +++ b/src/server/api/endpoints/notes/replies.ts @@ -1,9 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note, { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import { getFriends } from '../../common/get-friends'; -import { getHideUserIds } from '../../common/get-hide-users'; +import { Notes } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { generateMuteQuery } from '../../common/generate-mute-query'; export const meta = { desc: { @@ -18,22 +19,30 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID' } }, + sinceId: { + validator: $.optional.type(ID), + desc: { + 'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します' + } + }, + + untilId: { + validator: $.optional.type(ID), + desc: { + 'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します' + } + }, + limit: { validator: $.optional.num.range(1, 100), default: 10 }, - - offset: { - validator: $.optional.num.min(0), - default: 0 - }, }, res: { @@ -45,54 +54,14 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const [followings, hideUserIds] = await Promise.all([ - // フォローを取得 - // Fetch following - user ? getFriends(user._id) : [], - - // 隠すユーザーを取得 - getHideUserIds(user) - ]); - - const visibleQuery = user == null ? [{ - visibility: { $in: [ 'public', 'home' ] } - }] : [{ - visibility: { $in: [ 'public', 'home' ] } - }, { - // myself (for followers/specified/private) - userId: user._id - }, { - // to me (for specified) - visibleUserIds: { $in: [ user._id ] } - }, { - visibility: 'followers', - $or: [{ - // フォロワーの投稿 - userId: { $in: followings.map(f => f.id) }, - }, { - // 自分の投稿へのリプライ - '_reply.userId': user._id, - }, { - // 自分へのメンションが含まれている - mentions: { $in: [ user._id ] } - }] - }]; - - const q = { - replyId: ps.noteId, - $or: visibleQuery - } as any; + const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere('note.replyId = :replyId', { replyId: ps.noteId }) + .leftJoinAndSelect('note.user', 'user'); - if (hideUserIds && hideUserIds.length > 0) { - q['userId'] = { - $nin: hideUserIds - }; - } + if (user) generateVisibilityQuery(query, user); + if (user) generateMuteQuery(query, user); - const notes = await Note.find(q, { - limit: ps.limit, - skip: ps.offset - }); + const timeline = await query.take(ps.limit).getMany(); - return await packMany(notes, user); + return await Notes.packMany(timeline, user); }); diff --git a/src/server/api/endpoints/notes/search-by-tag.ts b/src/server/api/endpoints/notes/search-by-tag.ts index b33c884049..48de88d36e 100644 --- a/src/server/api/endpoints/notes/search-by-tag.ts +++ b/src/server/api/endpoints/notes/search-by-tag.ts @@ -1,10 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note from '../../../../models/note'; -import { getFriendIds } from '../../common/get-friends'; -import { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import { getHideUserIds } from '../../common/get-hide-users'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { Notes } from '../../../../models'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { Brackets } from 'typeorm'; export const meta = { desc: { @@ -28,16 +29,6 @@ export const meta = { } }, - following: { - validator: $.optional.nullable.bool, - default: null as any - }, - - mute: { - validator: $.optional.str, - default: 'mute_all' - }, - reply: { validator: $.optional.nullable.bool, default: null as any, @@ -61,44 +52,28 @@ export const meta = { } }, - media: { + poll: { validator: $.optional.nullable.bool, default: null as any, - deprecated: true, desc: { - 'ja-JP': 'ファイルが添付された投稿に限定するか否か (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' + 'ja-JP': 'アンケートが添付された投稿に限定するか否か' } }, - poll: { - validator: $.optional.nullable.bool, - default: null as any, + sinceId: { + validator: $.optional.type(ID), desc: { - 'ja-JP': 'アンケートが添付された投稿に限定するか否か' + 'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します' } }, untilId: { validator: $.optional.type(ID), - transform: transform, desc: { - 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' + 'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します' } }, - sinceDate: { - validator: $.optional.num, - }, - - untilDate: { - validator: $.optional.num, - }, - - offset: { - validator: $.optional.num.min(0), - default: 0 - }, - limit: { validator: $.optional.num.range(1, 30), default: 10 @@ -114,226 +89,58 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const visibleQuery = me == null ? [{ - visibility: { $in: [ 'public', 'home' ] } - }] : [{ - visibility: { $in: [ 'public', 'home' ] } - }, { - // myself (for specified/private) - userId: me._id - }, { - // to me (for specified) - visibleUserIds: { $in: [ me._id ] } - }]; - - const q: any = { - $and: [ps.tag ? { - tagsLower: ps.tag.toLowerCase() - } : { - $or: ps.query.map(tags => ({ - $and: tags.map(t => ({ - tagsLower: t.toLowerCase() - })) - })) - }], - deletedAt: { $exists: false }, - $or: visibleQuery - }; - - const push = (x: any) => q.$and.push(x); + const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .leftJoinAndSelect('note.user', 'user'); - if (ps.following != null && me != null) { - const ids = await getFriendIds(me._id, false); - push({ - userId: ps.following ? { - $in: ids - } : { - $nin: ids.concat(me._id) - } - }); - } + if (me) generateVisibilityQuery(query, me); + if (me) generateMuteQuery(query, me); - if (me != null) { - const hideUserIds = await getHideUserIds(me); - - switch (ps.mute) { - case 'mute_all': - push({ - userId: { - $nin: hideUserIds - }, - '_reply.userId': { - $nin: hideUserIds - }, - '_renote.userId': { - $nin: hideUserIds - } - }); - break; - case 'mute_related': - push({ - '_reply.userId': { - $nin: hideUserIds - }, - '_renote.userId': { - $nin: hideUserIds - } - }); - break; - case 'mute_direct': - push({ - userId: { - $nin: hideUserIds + if (ps.tag) { + query.andWhere(':tag = ANY(note.tags)', { tag: ps.tag }); + } else { + let i = 0; + query.andWhere(new Brackets(qb => { + for (const tags of ps.query) { + qb.orWhere(new Brackets(qb => { + for (const tag of tags) { + qb.andWhere(`:tag${i} = ANY(note.tags)`, { [`tag${i}`]: tag }); + i++; } - }); - break; - case 'direct_only': - push({ - userId: { - $in: hideUserIds - } - }); - break; - case 'related_only': - push({ - $or: [{ - '_reply.userId': { - $in: hideUserIds - } - }, { - '_renote.userId': { - $in: hideUserIds - } - }] - }); - break; - case 'all_only': - push({ - $or: [{ - userId: { - $in: hideUserIds - } - }, { - '_reply.userId': { - $in: hideUserIds - } - }, { - '_renote.userId': { - $in: hideUserIds - } - }] - }); - break; - } + })); + } + })); } if (ps.reply != null) { if (ps.reply) { - push({ - replyId: { - $exists: true, - $ne: null - } - }); + query.andWhere('note.replyId IS NOT NULL'); } else { - push({ - $or: [{ - replyId: { - $exists: false - } - }, { - replyId: null - }] - }); + query.andWhere('note.replyId IS NULL'); } } if (ps.renote != null) { if (ps.renote) { - push({ - renoteId: { - $exists: true, - $ne: null - } - }); + query.andWhere('note.renoteId IS NOT NULL'); } else { - push({ - $or: [{ - renoteId: { - $exists: false - } - }, { - renoteId: null - }] - }); + query.andWhere('note.renoteId IS NULL'); } } - const withFiles = ps.withFiles != null ? ps.withFiles : ps.media; - - if (withFiles) { - push({ - fileIds: { $exists: true, $ne: [] } - }); + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } if (ps.poll != null) { if (ps.poll) { - push({ - poll: { - $exists: true, - $ne: null - } - }); + query.andWhere('note.hasPoll = TRUE'); } else { - push({ - $or: [{ - poll: { - $exists: false - } - }, { - poll: null - }] - }); + query.andWhere('note.hasPoll = FALSE'); } } - if (ps.untilId) { - push({ - _id: { - $lt: ps.untilId - } - }); - } - - if (ps.sinceDate) { - push({ - createdAt: { - $gt: new Date(ps.sinceDate) - } - }); - } - - if (ps.untilDate) { - push({ - createdAt: { - $lt: new Date(ps.untilDate) - } - }); - } - - if (q.$and.length == 0) { - delete q.$and; - } - // Search notes - const notes = await Note.find(q, { - sort: { - _id: -1 - }, - limit: ps.limit, - skip: ps.offset - }); + const notes = await query.take(ps.limit).getMany(); - return await packMany(notes, me); + return await Notes.packMany(notes, me); }); diff --git a/src/server/api/endpoints/notes/search.ts b/src/server/api/endpoints/notes/search.ts index edc8a14560..cc88fb9380 100644 --- a/src/server/api/endpoints/notes/search.ts +++ b/src/server/api/endpoints/notes/search.ts @@ -1,10 +1,9 @@ import $ from 'cafy'; -import * as mongo from 'mongodb'; -import Note from '../../../../models/note'; -import { packMany } from '../../../../models/note'; import es from '../../../../db/elasticsearch'; import define from '../../define'; import { ApiError } from '../../error'; +import { Notes } from '../../../../models'; +import { In } from 'typeorm'; export const meta = { desc: { @@ -74,18 +73,19 @@ export default define(meta, async (ps, me) => { return []; } - const hits = response.hits.hits.map(hit => new mongo.ObjectID(hit._id)); + const hits = response.hits.hits.map((hit: any) => hit.id); + + if (hits.length === 0) return []; // Fetch found notes - const notes = await Note.find({ - _id: { - $in: hits - } - }, { - sort: { - _id: -1 + const notes = await Notes.find({ + where: { + id: In(hits) + }, + order: { + id: -1 } }); - return await packMany(notes, me); + return await Notes.packMany(notes, me); }); diff --git a/src/server/api/endpoints/notes/show.ts b/src/server/api/endpoints/notes/show.ts index 6d8dc73ff2..d41dc20c54 100644 --- a/src/server/api/endpoints/notes/show.ts +++ b/src/server/api/endpoints/notes/show.ts @@ -1,9 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import { pack } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { getNote } from '../../common/getters'; import { ApiError } from '../../error'; +import { Notes } from '../../../../models'; export const meta = { stability: 'stable', @@ -20,7 +20,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' @@ -47,7 +46,7 @@ export default define(meta, async (ps, user) => { throw e; }); - return await pack(note, user, { + return await Notes.pack(note, user, { detail: true }); }); diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/social-timeline.ts index 9695547f04..10e215d6c4 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/social-timeline.ts @@ -1,17 +1,18 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note from '../../../../models/note'; -import { getFriends } from '../../common/get-friends'; -import { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import fetchMeta from '../../../../misc/fetch-meta'; -import activeUsersChart from '../../../../services/chart/active-users'; -import { getHideUserIds } from '../../common/get-hide-users'; import { ApiError } from '../../error'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { Followings, Notes } from '../../../../models'; +import { Brackets } from 'typeorm'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { activeUsersChart } from '../../../../services/chart'; export const meta = { desc: { - 'ja-JP': 'ハイブリッドタイムラインを取得します。' + 'ja-JP': 'ソーシャルタイムラインを取得します。' }, tags: ['notes'], @@ -27,17 +28,15 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, desc: { - 'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します' + 'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します' } }, untilId: { validator: $.optional.type(ID), - transform: transform, desc: { - 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' + 'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します' } }, @@ -85,14 +84,6 @@ export const meta = { 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' } }, - - mediaOnly: { - validator: $.optional.bool, - deprecated: true, - desc: { - 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' - } - }, }, res: { @@ -112,94 +103,30 @@ export const meta = { }; export default define(meta, async (ps, user) => { + // TODO どっかにキャッシュ const m = await fetchMeta(); if (m.disableLocalTimeline && !user.isAdmin && !user.isModerator) { throw new ApiError(meta.errors.stlDisabled); } - const [followings, hideUserIds] = await Promise.all([ - // フォローを取得 - // Fetch following - getFriends(user._id, true, false), - - // 隠すユーザーを取得 - getHideUserIds(user) - ]); - //#region Construct query - const sort = { - _id: -1 - }; - - const followQuery = followings.map(f => ({ - userId: f.id, + const followingQuery = Followings.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: user.id }); - /*// リプライは含めない(ただし投稿者自身の投稿へのリプライ、自分の投稿へのリプライ、自分のリプライは含める) - $or: [{ - // リプライでない - replyId: null - }, { // または - // リプライだが返信先が投稿者自身の投稿 - $expr: { - $eq: ['$_reply.userId', '$userId'] - } - }, { // または - // リプライだが返信先が自分(フォロワー)の投稿 - '_reply.userId': user._id - }, { // または - // 自分(フォロワー)が送信したリプライ - userId: user._id - }]*/ - })); + const query = makePaginationQuery(Notes.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere(new Brackets(qb => { + qb.where(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: user.id }) + .orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)'); + })) + .leftJoinAndSelect('note.user', 'user') + .setParameters(followingQuery.getParameters()); - const visibleQuery = user == null ? [{ - visibility: { $in: ['public', 'home'] } - }] : [{ - visibility: { $in: ['public', 'home', 'followers'] } - }, { - // myself (for specified/private) - userId: user._id - }, { - // to me (for specified) - visibleUserIds: { $in: [ user._id ] } - }]; - - const query = { - $and: [{ - deletedAt: null, - - $or: [{ - $and: [{ - // フォローしている人の投稿 - $or: followQuery - }, { - // visible for me - $or: visibleQuery - }] - }, { - // public only - visibility: 'public', - - // リプライでない - //replyId: null, - - // local - '_user.host': null - }], - - // hide - userId: { - $nin: hideUserIds - }, - '_reply.userId': { - $nin: hideUserIds - }, - '_renote.userId': { - $nin: hideUserIds - }, - }] - } as any; + generateVisibilityQuery(query, user); + generateMuteQuery(query, user); + /* TODO // MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。 // つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。 // for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws @@ -207,7 +134,7 @@ export default define(meta, async (ps, user) => { if (ps.includeMyRenotes === false) { query.$and.push({ $or: [{ - userId: { $ne: user._id } + userId: { $ne: user.id } }, { renoteId: null }, { @@ -223,7 +150,7 @@ export default define(meta, async (ps, user) => { if (ps.includeRenotedMyNotes === false) { query.$and.push({ $or: [{ - '_renote.userId': { $ne: user._id } + '_renote.userId': { $ne: user.id } }, { renoteId: null }, { @@ -251,40 +178,18 @@ export default define(meta, async (ps, user) => { }] }); } + */ - if (ps.withFiles || ps.mediaOnly) { - query.$and.push({ - fileIds: { $exists: true, $ne: [] } - }); - } - - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } else if (ps.sinceDate) { - sort._id = 1; - query.createdAt = { - $gt: new Date(ps.sinceDate) - }; - } else if (ps.untilDate) { - query.createdAt = { - $lt: new Date(ps.untilDate) - }; + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } //#endregion - const timeline = await Note.find(query, { - limit: ps.limit, - sort: sort - }); + const timeline = await query.take(ps.limit).getMany(); - activeUsersChart.update(user); + if (user) { + activeUsersChart.update(user); + } - return await packMany(timeline, user); + return await Notes.packMany(timeline, user); }); diff --git a/src/server/api/endpoints/notes/state.ts b/src/server/api/endpoints/notes/state.ts index 4944802849..df1d9d9fb0 100644 --- a/src/server/api/endpoints/notes/state.ts +++ b/src/server/api/endpoints/notes/state.ts @@ -1,8 +1,7 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import Favorite from '../../../../models/favorite'; -import NoteWatching from '../../../../models/note-watching'; +import { NoteFavorites, NoteWatchings } from '../../../../models'; export const meta = { stability: 'stable', @@ -19,7 +18,6 @@ export const meta = { params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' @@ -30,17 +28,19 @@ export const meta = { export default define(meta, async (ps, user) => { const [favorite, watching] = await Promise.all([ - Favorite.count({ - userId: user._id, + NoteFavorites.count({ + where: { + userId: user.id, noteId: ps.noteId - }, { - limit: 1 + }, + take: 1 }), - NoteWatching.count({ - userId: user._id, + NoteWatchings.count({ + where: { + userId: user.id, noteId: ps.noteId - }, { - limit: 1 + }, + take: 1 }) ]); diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index 6ff7690c74..e22db4d1b0 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -1,11 +1,12 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note from '../../../../models/note'; -import { getFriends } from '../../common/get-friends'; -import { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import activeUsersChart from '../../../../services/chart/active-users'; -import { getHideUserIds } from '../../common/get-hide-users'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { Notes, Followings } from '../../../../models'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { activeUsersChart } from '../../../../services/chart'; +import { Brackets } from 'typeorm'; export const meta = { desc: { @@ -28,17 +29,15 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, desc: { - 'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します' + 'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します' } }, untilId: { validator: $.optional.type(ID), - transform: transform, desc: { - 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' + 'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します' } }, @@ -86,14 +85,6 @@ export const meta = { 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' } }, - - mediaOnly: { - validator: $.optional.bool, - deprecated: true, - desc: { - 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' - } - }, }, res: { @@ -105,78 +96,24 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const [followings, hideUserIds] = await Promise.all([ - // フォローを取得 - // Fetch following - getFriends(user._id), - - // 隠すユーザーを取得 - getHideUserIds(user) - ]); - //#region Construct query - const sort = { - _id: -1 - }; - - const followQuery = followings.map(f => ({ - userId: f.id, - - /*// リプライは含めない(ただし投稿者自身の投稿へのリプライ、自分の投稿へのリプライ、自分のリプライは含める) - $or: [{ - // リプライでない - replyId: null - }, { // または - // リプライだが返信先が投稿者自身の投稿 - $expr: { - $eq: ['$_reply.userId', '$userId'] - } - }, { // または - // リプライだが返信先が自分(フォロワー)の投稿 - '_reply.userId': user._id - }, { // または - // 自分(フォロワー)が送信したリプライ - userId: user._id - }]*/ - })); + const followingQuery = Followings.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: user.id }); - const visibleQuery = user == null ? [{ - visibility: { $in: [ 'public', 'home' ] } - }] : [{ - visibility: { $in: [ 'public', 'home', 'followers' ] } - }, { - // myself (for specified/private) - userId: user._id - }, { - // to me (for specified) - visibleUserIds: { $in: [ user._id ] } - }]; + const query = makePaginationQuery(Notes.createQueryBuilder('note'), + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere(new Brackets(qb => { qb + .where(`note.userId IN (${ followingQuery.getQuery() })`) + .orWhere('note.userId = :meId', { meId: user.id }); + })) + .leftJoinAndSelect('note.user', 'user') + .setParameters(followingQuery.getParameters()); - const query = { - $and: [{ - deletedAt: null, - - $and: [{ - // フォローしている人の投稿 - $or: followQuery - }, { - // visible for me - $or: visibleQuery - }], - - // mute - userId: { - $nin: hideUserIds - }, - '_reply.userId': { - $nin: hideUserIds - }, - '_renote.userId': { - $nin: hideUserIds - }, - }] - } as any; + generateVisibilityQuery(query, user); + generateMuteQuery(query, user); + /* v11 TODO // MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。 // つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。 // for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws @@ -184,7 +121,7 @@ export default define(meta, async (ps, user) => { if (ps.includeMyRenotes === false) { query.$and.push({ $or: [{ - userId: { $ne: user._id } + userId: { $ne: user.id } }, { renoteId: null }, { @@ -200,7 +137,7 @@ export default define(meta, async (ps, user) => { if (ps.includeRenotedMyNotes === false) { query.$and.push({ $or: [{ - '_renote.userId': { $ne: user._id } + '_renote.userId': { $ne: user.id } }, { renoteId: null }, { @@ -227,43 +164,16 @@ export default define(meta, async (ps, user) => { poll: { $ne: null } }] }); - } - - const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; - - if (withFiles) { - query.$and.push({ - fileIds: { $exists: true, $ne: [] } - }); - } + }*/ - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } else if (ps.sinceDate) { - sort._id = 1; - query.createdAt = { - $gt: new Date(ps.sinceDate) - }; - } else if (ps.untilDate) { - query.createdAt = { - $lt: new Date(ps.untilDate) - }; + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } //#endregion - const timeline = await Note.find(query, { - limit: ps.limit, - sort: sort - }); + const timeline = await query.take(ps.limit).getMany(); activeUsersChart.update(user); - return await packMany(timeline, user); + return await Notes.packMany(timeline, user); }); diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts index 17c24ab119..deda04acb4 100644 --- a/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/src/server/api/endpoints/notes/user-list-timeline.ts @@ -1,12 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note from '../../../../models/note'; -import { packMany } from '../../../../models/note'; -import UserList from '../../../../models/user-list'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import { getFriends } from '../../common/get-friends'; -import { getHideUserIds } from '../../common/get-hide-users'; import { ApiError } from '../../error'; +import { UserLists, UserListJoinings, Notes } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { activeUsersChart } from '../../../../services/chart'; export const meta = { desc: { @@ -21,7 +20,6 @@ export const meta = { params: { listId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': 'リストのID' } @@ -37,17 +35,15 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, desc: { - 'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します' + 'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します' } }, untilId: { validator: $.optional.type(ID), - transform: transform, desc: { - 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' + 'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します' } }, @@ -95,14 +91,6 @@ export const meta = { 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します' } }, - - mediaOnly: { - validator: $.optional.bool, - deprecated: true, - desc: { - 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' - } - }, }, res: { @@ -122,94 +110,28 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const [list, followings, hideUserIds] = await Promise.all([ - // リストを取得 - // Fetch the list - UserList.findOne({ - _id: ps.listId, - userId: user._id - }), - - // フォローを取得 - // Fetch following - getFriends(user._id, true, false), - - // 隠すユーザーを取得 - getHideUserIds(user) - ]); + const list = await UserLists.findOne({ + id: ps.listId, + userId: user.id + }); if (list == null) { throw new ApiError(meta.errors.noSuchList); } - if (list.userIds.length == 0) { - return []; - } - //#region Construct query - const sort = { - _id: -1 - }; - - const listQuery = list.userIds.map(u => ({ - userId: u, - - /*// リプライは含めない(ただし投稿者自身の投稿へのリプライ、自分の投稿へのリプライ、自分のリプライは含める) - $or: [{ - // リプライでない - replyId: null - }, { // または - // リプライだが返信先が投稿者自身の投稿 - $expr: { - $eq: ['$_reply.userId', '$userId'] - } - }, { // または - // リプライだが返信先が自分(フォロワー)の投稿 - '_reply.userId': user._id - }, { // または - // 自分(フォロワー)が送信したリプライ - userId: user._id - }]*/ - })); - - const visibleQuery = [{ - visibility: { $in: ['public', 'home'] } - }, { - // myself (for specified/private) - userId: user._id - }, { - // to me (for specified) - visibleUserIds: { $in: [user._id] } - }, { - visibility: 'followers', - userId: { $in: followings.map(f => f.id) } - }]; - - const query = { - $and: [{ - deletedAt: null, + const listQuery = UserListJoinings.createQueryBuilder('joining') + .select('joining.userId') + .where('joining.userListId = :userListId', { userListId: list.id }); - $and: [{ - // リストに入っている人のタイムラインへの投稿 - $or: listQuery - }, { - // visible for me - $or: visibleQuery - }], + const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere(`note.userId IN (${ listQuery.getQuery() })`) + .leftJoinAndSelect('note.user', 'user') + .setParameters(listQuery.getParameters()); - // mute - userId: { - $nin: hideUserIds - }, - '_reply.userId': { - $nin: hideUserIds - }, - '_renote.userId': { - $nin: hideUserIds - }, - }] - } as any; + generateVisibilityQuery(query, user); + /* TODO // MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。 // つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。 // for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws @@ -217,7 +139,7 @@ export default define(meta, async (ps, user) => { if (ps.includeMyRenotes === false) { query.$and.push({ $or: [{ - userId: { $ne: user._id } + userId: { $ne: user.id } }, { renoteId: null }, { @@ -233,7 +155,7 @@ export default define(meta, async (ps, user) => { if (ps.includeRenotedMyNotes === false) { query.$and.push({ $or: [{ - '_renote.userId': { $ne: user._id } + '_renote.userId': { $ne: user.id } }, { renoteId: null }, { @@ -260,41 +182,16 @@ export default define(meta, async (ps, user) => { poll: { $ne: null } }] }); - } - - const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; - - if (withFiles) { - query.$and.push({ - fileIds: { $exists: true, $ne: [] } - }); - } + }*/ - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - query._id = { - $lt: ps.untilId - }; - } else if (ps.sinceDate) { - sort._id = 1; - query.createdAt = { - $gt: new Date(ps.sinceDate) - }; - } else if (ps.untilDate) { - query.createdAt = { - $lt: new Date(ps.untilDate) - }; + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); } //#endregion - const timeline = await Note.find(query, { - limit: ps.limit, - sort: sort - }); + const timeline = await query.take(ps.limit).getMany(); + + activeUsersChart.update(user); - return await packMany(timeline, user); + return await Notes.packMany(timeline, user); }); diff --git a/src/server/api/endpoints/notes/watching/create.ts b/src/server/api/endpoints/notes/watching/create.ts index 2b2de1bd3b..b4045fe93c 100644 --- a/src/server/api/endpoints/notes/watching/create.ts +++ b/src/server/api/endpoints/notes/watching/create.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import watch from '../../../../../services/note/watch'; import { getNote } from '../../../common/getters'; @@ -17,12 +17,11 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' @@ -45,5 +44,5 @@ export default define(meta, async (ps, user) => { throw e; }); - await watch(user._id, note); + await watch(user.id, note); }); diff --git a/src/server/api/endpoints/notes/watching/delete.ts b/src/server/api/endpoints/notes/watching/delete.ts index 512db793ea..a272ecc37d 100644 --- a/src/server/api/endpoints/notes/watching/delete.ts +++ b/src/server/api/endpoints/notes/watching/delete.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import unwatch from '../../../../../services/note/unwatch'; import { getNote } from '../../../common/getters'; @@ -17,12 +17,11 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { noteId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象の投稿のID', 'en-US': 'Target note ID.' @@ -45,5 +44,5 @@ export default define(meta, async (ps, user) => { throw e; }); - await unwatch(user._id, note); + await unwatch(user.id, note); }); diff --git a/src/server/api/endpoints/notifications/mark-all-as-read.ts b/src/server/api/endpoints/notifications/mark-all-as-read.ts index e5df648285..9f34a32e80 100644 --- a/src/server/api/endpoints/notifications/mark-all-as-read.ts +++ b/src/server/api/endpoints/notifications/mark-all-as-read.ts @@ -1,7 +1,6 @@ -import Notification from '../../../../models/notification'; import { publishMainStream } from '../../../../services/stream'; -import User from '../../../../models/user'; import define from '../../define'; +import { Notifications } from '../../../../models'; export const meta = { desc: { @@ -13,29 +12,18 @@ export const meta = { requireCredential: true, - kind: 'notification-write' + kind: 'write:notifications' }; export default define(meta, async (ps, user) => { // Update documents - await Notification.update({ - notifieeId: user._id, - isRead: false + await Notifications.update({ + notifieeId: user.id, + isRead: false, }, { - $set: { - isRead: true - } - }, { - multi: true - }); - - // Update flag - User.update({ _id: user._id }, { - $set: { - hasUnreadNotification: false - } + isRead: true }); // 全ての通知を読みましたよというイベントを発行 - publishMainStream(user._id, 'readAllNotifications'); + publishMainStream(user.id, 'readAllNotifications'); }); diff --git a/src/server/api/endpoints/stats.ts b/src/server/api/endpoints/stats.ts index 30c49cdd86..f3ebaa16ad 100644 --- a/src/server/api/endpoints/stats.ts +++ b/src/server/api/endpoints/stats.ts @@ -1,7 +1,6 @@ import define from '../define'; -import driveChart from '../../../services/chart/drive'; -import federationChart from '../../../services/chart/federation'; -import fetchMeta from '../../../misc/fetch-meta'; +import { Notes, Users } from '../../../models'; +import { federationChart, driveChart } from '../../../services/chart'; export const meta = { requireCredential: false, @@ -43,16 +42,17 @@ export const meta = { }; export default define(meta, async () => { - const instance = await fetchMeta(); + const [notesCount, originalNotesCount, usersCount, originalUsersCount, instances, driveUsageLocal, driveUsageRemote] = await Promise.all([ + Notes.count(), + Notes.count({ userHost: null }), + Users.count(), + Users.count({ host: null }), + federationChart.getChart('hour', 1).then(chart => chart.instance.total[0]), + driveChart.getChart('hour', 1).then(chart => chart.local.totalSize[0]), + driveChart.getChart('hour', 1).then(chart => chart.remote.totalSize[0]), + ]); - const stats: any = instance.stats; - - const driveStats = await driveChart.getChart('hour', 1); - stats.driveUsageLocal = driveStats.local.totalSize[0]; - stats.driveUsageRemote = driveStats.remote.totalSize[0]; - - const federationStats = await federationChart.getChart('hour', 1); - stats.instances = federationStats.instance.total[0]; - - return stats; + return { + notesCount, originalNotesCount, usersCount, originalUsersCount, instances, driveUsageLocal, driveUsageRemote + }; }); diff --git a/src/server/api/endpoints/sw/register.ts b/src/server/api/endpoints/sw/register.ts index 0b81b06abe..cb0572aa90 100644 --- a/src/server/api/endpoints/sw/register.ts +++ b/src/server/api/endpoints/sw/register.ts @@ -1,7 +1,8 @@ import $ from 'cafy'; -import Subscription from '../../../../models/sw-subscription'; import define from '../../define'; import fetchMeta from '../../../../misc/fetch-meta'; +import { genId } from '../../../../misc/gen-id'; +import { SwSubscriptions } from '../../../../models'; export const meta = { tags: ['account'], @@ -25,12 +26,11 @@ export const meta = { export default define(meta, async (ps, user) => { // if already subscribed - const exist = await Subscription.findOne({ - userId: user._id, + const exist = await SwSubscriptions.findOne({ + userId: user.id, endpoint: ps.endpoint, auth: ps.auth, publickey: ps.publickey, - deletedAt: { $exists: false } }); const instance = await fetchMeta(); @@ -42,8 +42,9 @@ export default define(meta, async (ps, user) => { }; } - await Subscription.insert({ - userId: user._id, + await SwSubscriptions.save({ + id: genId(), + userId: user.id, endpoint: ps.endpoint, auth: ps.auth, publickey: ps.publickey diff --git a/src/server/api/endpoints/username/available.ts b/src/server/api/endpoints/username/available.ts index 1d098eb399..42ab176652 100644 --- a/src/server/api/endpoints/username/available.ts +++ b/src/server/api/endpoints/username/available.ts @@ -1,7 +1,6 @@ import $ from 'cafy'; -import User from '../../../../models/user'; -import { validateUsername } from '../../../../models/user'; import define from '../../define'; +import { Users } from '../../../../models'; export const meta = { tags: ['users'], @@ -10,18 +9,16 @@ export const meta = { params: { username: { - validator: $.str.pipe(validateUsername) + validator: $.str.pipe(Users.validateUsername) } } }; export default define(meta, async (ps) => { // Get exist - const exist = await User.count({ + const exist = await Users.count({ host: null, usernameLower: ps.username.toLowerCase() - }, { - limit: 1 }); return { diff --git a/src/server/api/endpoints/users.ts b/src/server/api/endpoints/users.ts index be83dcd9cc..f99165f3d5 100644 --- a/src/server/api/endpoints/users.ts +++ b/src/server/api/endpoints/users.ts @@ -1,10 +1,7 @@ import $ from 'cafy'; -import User, { pack } from '../../../models/user'; import define from '../define'; -import { fallback } from '../../../prelude/symbol'; -import { getHideUserIds } from '../common/get-hide-users'; - -const nonnull = { $ne: null as any }; +import { Users } from '../../../models'; +import { generateMuteQueryForUsers } from '../common/generate-mute-query'; export const meta = { tags: ['users'], @@ -63,53 +60,38 @@ export const meta = { }, }; -const state: any = { // < https://github.com/Microsoft/TypeScript/issues/1863 - 'admin': { isAdmin: true }, - 'moderator': { isModerator: true }, - 'adminOrModerator': { - $or: [ - { isAdmin: true }, - { isModerator: true } - ] - }, - 'verified': { isVerified: true }, - 'alive': { - updatedAt: { $gt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) } - }, - [fallback]: {} -}; +export default define(meta, async (ps, me) => { + const query = Users.createQueryBuilder('user'); -const origin: any = { // < https://github.com/Microsoft/TypeScript/issues/1863 - 'local': { host: null }, - 'remote': { host: nonnull }, - [fallback]: {} -}; + switch (ps.state) { + case 'admin': query.where('user.isAdmin = TRUE'); break; + case 'moderator': query.where('user.isModerator = TRUE'); break; + case 'adminOrModerator': query.where('user.isAdmin = TRUE OR isModerator = TRUE'); break; + case 'verified': query.where('user.isVerified = TRUE'); break; + case 'alive': query.where('user.updatedAt > :date', { date: new Date(Date.now() - 1000 * 60 * 60 * 24 * 5) }); break; + } -const sort: any = { // < https://github.com/Microsoft/TypeScript/issues/1863 - '+follower': { followersCount: -1 }, - '-follower': { followersCount: 1 }, - '+createdAt': { createdAt: -1 }, - '-createdAt': { createdAt: 1 }, - '+updatedAt': { updatedAt: -1 }, - '-updatedAt': { updatedAt: 1 }, - [fallback]: { _id: -1 } -}; + switch (ps.origin) { + case 'local': query.andWhere('user.host IS NULL'); break; + case 'remote': query.andWhere('user.host IS NOT NULL'); break; + } -export default define(meta, async (ps, me) => { - const hideUserIds = await getHideUserIds(me); + switch (ps.sort) { + case '+follower': query.orderBy('user.followersCount', 'DESC'); break; + case '-follower': query.orderBy('user.followersCount', 'ASC'); break; + case '+createdAt': query.orderBy('user.createdAt', 'DESC'); break; + case '-createdAt': query.orderBy('user.createdAt', 'ASC'); break; + case '+updatedAt': query.orderBy('user.updatedAt', 'DESC'); break; + case '-updatedAt': query.orderBy('user.updatedAt', 'ASC'); break; + default: query.orderBy('user.id', 'ASC'); break; + } + + if (me) generateMuteQueryForUsers(query, me); + + query.take(ps.limit); + query.skip(ps.offset); - const users = await User - .find({ - $and: [ - state[ps.state] || state[fallback], - origin[ps.origin] || origin[fallback] - ], - ...(hideUserIds && hideUserIds.length > 0 ? { _id: { $nin: hideUserIds } } : {}) - }, { - limit: ps.limit, - sort: sort[ps.sort] || sort[fallback], - skip: ps.offset - }); + const users = await query.getMany(); - return await Promise.all(users.map(user => pack(user, me, { detail: true }))); + return await Users.packMany(users, me, { detail: true }); }); diff --git a/src/server/api/endpoints/users/followers.ts b/src/server/api/endpoints/users/followers.ts index 3c8290a8b1..51b007ddaa 100644 --- a/src/server/api/endpoints/users/followers.ts +++ b/src/server/api/endpoints/users/followers.ts @@ -1,11 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import User from '../../../../models/user'; -import Following from '../../../../models/following'; -import { pack } from '../../../../models/user'; -import { getFriendIds } from '../../common/get-friends'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { ApiError } from '../../error'; +import { Users, Followings } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { desc: { @@ -20,7 +18,6 @@ export const meta = { params: { userId: { validator: $.optional.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -35,38 +32,25 @@ export const meta = { validator: $.optional.nullable.str }, - limit: { - validator: $.optional.num.range(1, 100), - default: 10 + sinceId: { + validator: $.optional.type(ID), }, - cursor: { + untilId: { validator: $.optional.type(ID), - default: null as any, - transform: transform, }, - iknow: { - validator: $.optional.bool, - default: false, - } + limit: { + validator: $.optional.num.range(1, 100), + default: 10 + }, }, res: { - type: 'object', - properties: { - users: { - type: 'array', - items: { - type: 'User', - } - }, - next: { - type: 'string', - format: 'id', - nullable: true - } - } + type: 'array', + items: { + type: 'Following', + }, }, errors: { @@ -79,54 +63,20 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const q: any = ps.userId != null - ? { _id: ps.userId } - : { usernameLower: ps.username.toLowerCase(), host: ps.host }; - - const user = await User.findOne(q); + const user = await Users.findOne(ps.userId != null + ? { id: ps.userId } + : { usernameLower: ps.username.toLowerCase(), host: ps.host }); - if (user === null) { + if (user == null) { throw new ApiError(meta.errors.noSuchUser); } - const query = { - followeeId: user._id - } as any; - - // ログインしていてかつ iknow フラグがあるとき - if (me && ps.iknow) { - // Get my friends - const myFriends = await getFriendIds(me._id); - - query.followerId = { - $in: myFriends - }; - } - - // カーソルが指定されている場合 - if (ps.cursor) { - query._id = { - $lt: ps.cursor - }; - } - - // Get followers - const following = await Following - .find(query, { - limit: ps.limit + 1, - sort: { _id: -1 } - }); - - // 「次のページ」があるかどうか - const inStock = following.length === ps.limit + 1; - if (inStock) { - following.pop(); - } + const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) + .andWhere(`following.followeeId = :userId`, { userId: user.id }); - const users = await Promise.all(following.map(f => pack(f.followerId, me, { detail: true }))); + const followings = await query + .take(ps.limit) + .getMany(); - return { - users: users, - next: inStock ? following[following.length - 1]._id : null, - }; + return await Followings.packMany(followings, me, { populateFollower: true }); }); diff --git a/src/server/api/endpoints/users/following.ts b/src/server/api/endpoints/users/following.ts index 4bc740cad9..46550f0f77 100644 --- a/src/server/api/endpoints/users/following.ts +++ b/src/server/api/endpoints/users/following.ts @@ -1,11 +1,9 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import User from '../../../../models/user'; -import Following from '../../../../models/following'; -import { pack } from '../../../../models/user'; -import { getFriendIds } from '../../common/get-friends'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { ApiError } from '../../error'; +import { Users, Followings } from '../../../../models'; +import { makePaginationQuery } from '../../common/make-pagination-query'; export const meta = { desc: { @@ -20,7 +18,6 @@ export const meta = { params: { userId: { validator: $.optional.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -35,38 +32,25 @@ export const meta = { validator: $.optional.nullable.str }, - limit: { - validator: $.optional.num.range(1, 100), - default: 10 + sinceId: { + validator: $.optional.type(ID), }, - cursor: { + untilId: { validator: $.optional.type(ID), - default: null as any, - transform: transform, }, - iknow: { - validator: $.optional.bool, - default: false, - } + limit: { + validator: $.optional.num.range(1, 100), + default: 10 + }, }, res: { - type: 'object', - properties: { - users: { - type: 'array', - items: { - type: 'User', - } - }, - next: { - type: 'string', - format: 'id', - nullable: true - } - } + type: 'array', + items: { + type: 'Following', + }, }, errors: { @@ -79,54 +63,20 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const q: any = ps.userId != null - ? { _id: ps.userId } - : { usernameLower: ps.username.toLowerCase(), host: ps.host }; - - const user = await User.findOne(q); + const user = await Users.findOne(ps.userId != null + ? { id: ps.userId } + : { usernameLower: ps.username.toLowerCase(), host: ps.host }); - if (user === null) { + if (user == null) { throw new ApiError(meta.errors.noSuchUser); } - const query = { - followerId: user._id - } as any; - - // ログインしていてかつ iknow フラグがあるとき - if (me && ps.iknow) { - // Get my friends - const myFriends = await getFriendIds(me._id); - - query.followeeId = { - $in: myFriends - }; - } - - // カーソルが指定されている場合 - if (ps.cursor) { - query._id = { - $lt: ps.cursor - }; - } - - // Get followers - const following = await Following - .find(query, { - limit: ps.limit + 1, - sort: { _id: -1 } - }); - - // 「次のページ」があるかどうか - const inStock = following.length === ps.limit + 1; - if (inStock) { - following.pop(); - } + const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) + .andWhere(`following.followerId = :userId`, { userId: user.id }); - const users = await Promise.all(following.map(f => pack(f.followeeId, me, { detail: true }))); + const followings = await query + .take(ps.limit) + .getMany(); - return { - users: users, - next: inStock ? following[following.length - 1]._id : null, - }; + return await Followings.packMany(followings, me, { populateFollowee: true }); }); diff --git a/src/server/api/endpoints/users/get-frequently-replied-users.ts b/src/server/api/endpoints/users/get-frequently-replied-users.ts index 46c7fba2f6..f82f437629 100644 --- a/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -1,12 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note from '../../../../models/note'; -import { pack } from '../../../../models/user'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { maximum } from '../../../../prelude/array'; -import { getHideUserIds } from '../../common/get-hide-users'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { Not, In } from 'typeorm'; +import { Notes, Users } from '../../../../models'; export const meta = { tags: ['users'], @@ -16,7 +15,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -53,21 +51,16 @@ export default define(meta, async (ps, me) => { }); // Fetch recent notes - const recentNotes = await Note.find({ - userId: user._id, - replyId: { - $exists: true, - $ne: null - } - }, { - sort: { - _id: -1 + const recentNotes = await Notes.find({ + where: { + userId: user.id, + replyId: Not(null) }, - limit: 1000, - fields: { - _id: false, - replyId: true - } + order: { + id: -1 + }, + take: 1000, + select: ['replyId'] }); // 投稿が少なかったら中断 @@ -75,21 +68,12 @@ export default define(meta, async (ps, me) => { return []; } - const hideUserIds = await getHideUserIds(me); - hideUserIds.push(user._id); - - const replyTargetNotes = await Note.find({ - _id: { - $in: recentNotes.map(p => p.replyId) + // TODO ミュートを考慮 + const replyTargetNotes = await Notes.find({ + where: { + id: In(recentNotes.map(p => p.replyId)), }, - userId: { - $nin: hideUserIds - } - }, { - fields: { - _id: false, - userId: true - } + select: ['userId'] }); const repliedUsers: any = {}; @@ -114,7 +98,7 @@ export default define(meta, async (ps, me) => { // Make replies object (includes weights) const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({ - user: await pack(user, me, { detail: true }), + user: await Users.pack(user, me, { detail: true }), weight: repliedUsers[user] / peak }))); diff --git a/src/server/api/endpoints/users/lists/create.ts b/src/server/api/endpoints/users/lists/create.ts index 00d2538c9f..21dc6d331d 100644 --- a/src/server/api/endpoints/users/lists/create.ts +++ b/src/server/api/endpoints/users/lists/create.ts @@ -1,6 +1,8 @@ import $ from 'cafy'; -import UserList, { pack } from '../../../../../models/user-list'; import define from '../../../define'; +import { UserLists } from '../../../../../models'; +import { genId } from '../../../../../misc/gen-id'; +import { UserList } from '../../../../../models/entities/user-list'; export const meta = { desc: { @@ -12,7 +14,7 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { title: { @@ -22,12 +24,12 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const userList = await UserList.insert({ + const userList = await UserLists.save({ + id: genId(), createdAt: new Date(), - userId: user._id, - title: ps.title, - userIds: [] - }); + userId: user.id, + name: ps.title, + } as UserList); - return await pack(userList); + return await UserLists.pack(userList); }); diff --git a/src/server/api/endpoints/users/lists/delete.ts b/src/server/api/endpoints/users/lists/delete.ts index d8faaa928c..0634bca4e3 100644 --- a/src/server/api/endpoints/users/lists/delete.ts +++ b/src/server/api/endpoints/users/lists/delete.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import UserList from '../../../../../models/user-list'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { UserLists } from '../../../../../models'; export const meta = { desc: { @@ -14,12 +14,11 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { listId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象となるユーザーリストのID', 'en-US': 'ID of target user list' @@ -37,16 +36,14 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const userList = await UserList.findOne({ - _id: ps.listId, - userId: user._id + const userList = await UserLists.findOne({ + id: ps.listId, + userId: user.id }); if (userList == null) { throw new ApiError(meta.errors.noSuchList); } - await UserList.remove({ - _id: userList._id - }); + await UserLists.delete(userList.id); }); diff --git a/src/server/api/endpoints/users/lists/list.ts b/src/server/api/endpoints/users/lists/list.ts index ece2af5603..b05fc45527 100644 --- a/src/server/api/endpoints/users/lists/list.ts +++ b/src/server/api/endpoints/users/lists/list.ts @@ -1,5 +1,5 @@ -import UserList, { pack } from '../../../../../models/user-list'; import define from '../../../define'; +import { UserLists } from '../../../../../models'; export const meta = { desc: { @@ -10,7 +10,7 @@ export const meta = { requireCredential: true, - kind: 'account-read', + kind: 'read:account', res: { type: 'array', @@ -21,9 +21,9 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const userLists = await UserList.find({ - userId: me._id, + const userLists = await UserLists.find({ + userId: me.id, }); - return await Promise.all(userLists.map(x => pack(x))); + return await Promise.all(userLists.map(x => UserLists.pack(x))); }); diff --git a/src/server/api/endpoints/users/lists/pull.ts b/src/server/api/endpoints/users/lists/pull.ts index 0eee1975db..524670b341 100644 --- a/src/server/api/endpoints/users/lists/pull.ts +++ b/src/server/api/endpoints/users/lists/pull.ts @@ -1,11 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import UserList from '../../../../../models/user-list'; -import { pack as packUser } from '../../../../../models/user'; +import { ID } from '../../../../../misc/cafy-id'; import { publishUserListStream } from '../../../../../services/stream'; import define from '../../../define'; import { ApiError } from '../../../error'; import { getUser } from '../../../common/getters'; +import { UserLists, UserListJoinings, Users } from '../../../../../models'; export const meta = { desc: { @@ -17,17 +16,15 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { listId: { validator: $.type(ID), - transform: transform, }, userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -52,9 +49,9 @@ export const meta = { export default define(meta, async (ps, me) => { // Fetch the list - const userList = await UserList.findOne({ - _id: ps.listId, - userId: me._id, + const userList = await UserLists.findOne({ + id: ps.listId, + userId: me.id, }); if (userList == null) { @@ -68,11 +65,7 @@ export default define(meta, async (ps, me) => { }); // Pull the user - await UserList.update({ _id: userList._id }, { - $pull: { - userIds: user._id - } - }); + await UserListJoinings.delete({ userId: user.id }); - publishUserListStream(userList._id, 'userRemoved', await packUser(user)); + publishUserListStream(userList.id, 'userRemoved', await Users.pack(user)); }); diff --git a/src/server/api/endpoints/users/lists/push.ts b/src/server/api/endpoints/users/lists/push.ts index eea2f39a8c..2763b3a19c 100644 --- a/src/server/api/endpoints/users/lists/push.ts +++ b/src/server/api/endpoints/users/lists/push.ts @@ -1,10 +1,10 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import UserList from '../../../../../models/user-list'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; import { getUser } from '../../../common/getters'; import { pushUserToUserList } from '../../../../../services/user-list/push'; +import { UserLists, UserListJoinings } from '../../../../../models'; export const meta = { desc: { @@ -16,17 +16,15 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { listId: { validator: $.type(ID), - transform: transform, }, userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -57,9 +55,9 @@ export const meta = { export default define(meta, async (ps, me) => { // Fetch the list - const userList = await UserList.findOne({ - _id: ps.listId, - userId: me._id, + const userList = await UserLists.findOne({ + id: ps.listId, + userId: me.id, }); if (userList == null) { @@ -72,7 +70,12 @@ export default define(meta, async (ps, me) => { throw e; }); - if (userList.userIds.map(id => id.toHexString()).includes(user._id.toHexString())) { + const exist = await UserListJoinings.findOne({ + userListId: userList.id, + userId: user.id + }); + + if (exist) { throw new ApiError(meta.errors.alreadyAdded); } diff --git a/src/server/api/endpoints/users/lists/show.ts b/src/server/api/endpoints/users/lists/show.ts index 0fab2fa499..1a997ec7c5 100644 --- a/src/server/api/endpoints/users/lists/show.ts +++ b/src/server/api/endpoints/users/lists/show.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import UserList, { pack } from '../../../../../models/user-list'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { UserLists } from '../../../../../models'; export const meta = { desc: { @@ -14,12 +14,11 @@ export const meta = { requireCredential: true, - kind: 'account-read', + kind: 'read:account', params: { listId: { validator: $.type(ID), - transform: transform, }, }, @@ -38,14 +37,14 @@ export const meta = { export default define(meta, async (ps, me) => { // Fetch the list - const userList = await UserList.findOne({ - _id: ps.listId, - userId: me._id, + const userList = await UserLists.findOne({ + id: ps.listId, + userId: me.id, }); if (userList == null) { throw new ApiError(meta.errors.noSuchList); } - return await pack(userList); + return await UserLists.pack(userList); }); diff --git a/src/server/api/endpoints/users/lists/update.ts b/src/server/api/endpoints/users/lists/update.ts index 5897693144..dc08d59f6a 100644 --- a/src/server/api/endpoints/users/lists/update.ts +++ b/src/server/api/endpoints/users/lists/update.ts @@ -1,8 +1,8 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../../misc/cafy-id'; -import UserList, { pack } from '../../../../../models/user-list'; +import { ID } from '../../../../../misc/cafy-id'; import define from '../../../define'; import { ApiError } from '../../../error'; +import { UserLists } from '../../../../../models'; export const meta = { desc: { @@ -14,19 +14,18 @@ export const meta = { requireCredential: true, - kind: 'account-write', + kind: 'write:account', params: { listId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象となるユーザーリストのID', 'en-US': 'ID of target user list' } }, - title: { + name: { validator: $.str.range(1, 100), desc: { 'ja-JP': 'このユーザーリストの名前', @@ -46,20 +45,18 @@ export const meta = { export default define(meta, async (ps, user) => { // Fetch the list - const userList = await UserList.findOne({ - _id: ps.listId, - userId: user._id + const userList = await UserLists.findOne({ + id: ps.listId, + userId: user.id }); if (userList == null) { throw new ApiError(meta.errors.noSuchList); } - await UserList.update({ _id: userList._id }, { - $set: { - title: ps.title - } + await UserLists.update(userList.id, { + name: ps.name }); - return await pack(userList._id); + return await UserLists.pack(userList.id); }); diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index 10d2f37fc2..6df394cbb1 100644 --- a/src/server/api/endpoints/users/notes.ts +++ b/src/server/api/endpoints/users/notes.ts @@ -1,10 +1,13 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; -import Note, { packMany } from '../../../../models/note'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import Following from '../../../../models/following'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query'; +import { Notes } from '../../../../models'; +import { generateMuteQuery } from '../../common/generate-mute-query'; +import { Brackets } from 'typeorm'; export const meta = { desc: { @@ -16,7 +19,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -42,17 +44,15 @@ export const meta = { sinceId: { validator: $.optional.type(ID), - transform: transform, desc: { - 'ja-JP': '指定すると、この投稿を基点としてより新しい投稿を取得します' + 'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します' } }, untilId: { validator: $.optional.type(ID), - transform: transform, desc: { - 'ja-JP': '指定すると、この投稿を基点としてより古い投稿を取得します' + 'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します' } }, @@ -102,15 +102,6 @@ export const meta = { } }, - mediaOnly: { - validator: $.optional.bool, - default: false, - deprecated: true, - desc: { - 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' - } - }, - fileType: { validator: $.optional.arr($.str), desc: { @@ -150,67 +141,44 @@ export default define(meta, async (ps, me) => { throw e; }); - const isFollowing = me == null ? false : ((await Following.findOne({ - followerId: me._id, - followeeId: user._id - })) != null); - //#region Construct query - const sort = { } as any; + const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) + .andWhere('note.userId = :userId', { userId: user.id }) + .leftJoinAndSelect('note.user', 'user'); - const visibleQuery = me == null ? [{ - visibility: { $in: ['public', 'home'] } - }] : [{ - visibility: { - $in: isFollowing ? ['public', 'home', 'followers'] : ['public', 'home'] - } - }, { - // myself (for specified/private) - userId: me._id - }, { - // to me (for specified) - visibleUserIds: { $in: [ me._id ] } - }]; + if (me) generateVisibilityQuery(query, me); + if (me) generateMuteQuery(query, me); + + if (ps.withFiles) { + query.andWhere('note.fileIds != \'{}\''); + } - const query = { - $and: [ {} ], - deletedAt: null, - userId: user._id, - $or: visibleQuery - } as any; + if (ps.fileType) { + query.andWhere('note.fileIds != \'{}\''); + query.andWhere(new Brackets(qb => { + for (const type of ps.fileType) { + const i = ps.fileType.indexOf(type); + qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type }); + } + })); - if (ps.sinceId) { - sort._id = 1; - query._id = { - $gt: ps.sinceId - }; - } else if (ps.untilId) { - sort._id = -1; - query._id = { - $lt: ps.untilId - }; - } else if (ps.sinceDate) { - sort.createdAt = 1; - query.createdAt = { - $gt: new Date(ps.sinceDate) - }; - } else if (ps.untilDate) { - sort.createdAt = -1; - query.createdAt = { - $lt: new Date(ps.untilDate) - }; - } else { - sort._id = -1; + if (ps.excludeNsfw) { + // v11 TODO + /*query['_files.isSensitive'] = { + $ne: true + };*/ + } } if (!ps.includeReplies) { - query.replyId = null; + query.andWhere('note.replyId IS NULL'); } + /* TODO if (ps.includeMyRenotes === false) { query.$and.push({ $or: [{ - userId: { $ne: user._id } + userId: { $ne: user.id } }, { renoteId: null }, { @@ -222,35 +190,11 @@ export default define(meta, async (ps, me) => { }] }); } + */ - const withFiles = ps.withFiles != null ? ps.withFiles : ps.mediaOnly; - - if (withFiles) { - query.fileIds = { - $exists: true, - $ne: [] - }; - } - - if (ps.fileType) { - query.fileIds = { $exists: true, $ne: [] }; - - query['_files.contentType'] = { - $in: ps.fileType - }; - - if (ps.excludeNsfw) { - query['_files.metadata.isSensitive'] = { - $ne: true - }; - } - } //#endregion - const notes = await Note.find(query, { - limit: ps.limit, - sort: sort - }); + const timeline = await query.take(ps.limit).getMany(); - return await packMany(notes, me); + return await Notes.packMany(timeline, user); }); diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts index 60710fffca..2c82d6613e 100644 --- a/src/server/api/endpoints/users/recommendation.ts +++ b/src/server/api/endpoints/users/recommendation.ts @@ -1,14 +1,8 @@ import * as ms from 'ms'; import $ from 'cafy'; -import User, { pack, ILocalUser } from '../../../../models/user'; -import { getFriendIds } from '../../common/get-friends'; -import * as request from 'request-promise-native'; -import config from '../../../../config'; import define from '../../define'; -import fetchMeta from '../../../../misc/fetch-meta'; -import resolveUser from '../../../../remote/resolve-user'; -import { getHideUserIds } from '../../common/get-hide-users'; -import { apiLogger } from '../../logger'; +import { Users, Followings } from '../../../../models'; +import { generateMuteQueryForUsers } from '../../common/generate-mute-query'; export const meta = { desc: { @@ -19,7 +13,7 @@ export const meta = { requireCredential: true, - kind: 'account-read', + kind: 'read:account', params: { limit: { @@ -42,83 +36,24 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const instance = await fetchMeta(); + const query = Users.createQueryBuilder('user') + .where('user.isLocked = FALSE') + .where('user.host IS NULL') + .where('user.updatedAt >= :date', { date: new Date(Date.now() - ms('7days')) }) + .orderBy('user.followersCount', 'DESC'); - if (instance.enableExternalUserRecommendation) { - const userName = me.username; - const hostName = config.hostname; - const limit = ps.limit; - const offset = ps.offset; - const timeout = instance.externalUserRecommendationTimeout; - const engine = instance.externalUserRecommendationEngine; - const url = engine - .replace('{{host}}', hostName) - .replace('{{user}}', userName) - .replace('{{limit}}', limit.toString()) - .replace('{{offset}}', offset.toString()); + generateMuteQueryForUsers(query, me); - const users = await request({ - url: url, - proxy: config.proxy, - timeout: timeout, - json: true, - followRedirect: true, - followAllRedirects: true - }) - .then(body => convertUsers(body, me)); + const followingQuery = Followings.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: me.id }); - return users; - } else { - // ID list of the user itself and other users who the user follows - const followingIds = await getFriendIds(me._id); + query + .andWhere(`user.id NOT IN (${ followingQuery.getQuery() })`); - // 隠すユーザーを取得 - const hideUserIds = await getHideUserIds(me); + query.setParameters(followingQuery.getParameters()); - const users = await User.find({ - _id: { - $nin: followingIds.concat(hideUserIds) - }, - isLocked: { $ne: true }, - updatedAt: { - $gte: new Date(Date.now() - ms('7days')) - }, - host: null - }, { - limit: ps.limit, - skip: ps.offset, - sort: { - followersCount: -1 - } - }); + const users = await query.take(ps.limit).skip(ps.offset).getMany(); - return await Promise.all(users.map(user => pack(user, me, { detail: true }))); - } + return await Users.packMany(users, me, { detail: true }); }); - -type IRecommendUser = { - name: string; - username: string; - host: string; - description: string; - avatarUrl: string; -}; - -/** - * Resolve/Pack dummy users - */ -async function convertUsers(src: IRecommendUser[], me: ILocalUser) { - const packed = await Promise.all(src.map(async x => { - const user = await resolveUser(x.username, x.host) - .catch(() => { - apiLogger.warn(`Can't resolve ${x.username}@${x.host}`); - return null; - }); - - if (user == null) return x; - - return await pack(user, me, { detail: true }); - })); - - return packed; -} diff --git a/src/server/api/endpoints/users/relation.ts b/src/server/api/endpoints/users/relation.ts index f4121aa0d0..4971738d32 100644 --- a/src/server/api/endpoints/users/relation.ts +++ b/src/server/api/endpoints/users/relation.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import ID, { transform, ObjectId } from '../../../../misc/cafy-id'; -import { getRelation } from '../../../../models/user'; import define from '../../define'; +import { ID } from '../../../../misc/cafy-id'; +import { Users } from '../../../../models'; export const meta = { desc: { @@ -15,7 +15,6 @@ export const meta = { params: { userId: { validator: $.either($.type(ID), $.arr($.type(ID)).unique()), - transform: (v: any): ObjectId | ObjectId[] => Array.isArray(v) ? v.map(x => transform(x)) : transform(v), desc: { 'ja-JP': 'ユーザーID (配列でも可)' } @@ -26,7 +25,7 @@ export const meta = { export default define(meta, async (ps, me) => { const ids = Array.isArray(ps.userId) ? ps.userId : [ps.userId]; - const relations = await Promise.all(ids.map(id => getRelation(me._id, id))); + const relations = await Promise.all(ids.map(id => Users.getRelation(me.id, id))); return Array.isArray(ps.userId) ? relations : relations[0]; }); diff --git a/src/server/api/endpoints/users/report-abuse.ts b/src/server/api/endpoints/users/report-abuse.ts index 0f23f8f0c3..2ee28c9002 100644 --- a/src/server/api/endpoints/users/report-abuse.ts +++ b/src/server/api/endpoints/users/report-abuse.ts @@ -1,11 +1,11 @@ import $ from 'cafy'; -import ID, { transform } from '../../../../misc/cafy-id'; +import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; -import User from '../../../../models/user'; -import AbuseUserReport from '../../../../models/abuse-user-report'; import { publishAdminStream } from '../../../../services/stream'; import { ApiError } from '../../error'; import { getUser } from '../../common/getters'; +import { AbuseUserReports, Users } from '../../../../models'; +import { genId } from '../../../../misc/gen-id'; export const meta = { desc: { @@ -19,7 +19,6 @@ export const meta = { params: { userId: { validator: $.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -62,7 +61,7 @@ export default define(meta, async (ps, me) => { throw e; }); - if (user._id.equals(me._id)) { + if (user.id === me.id) { throw new ApiError(meta.errors.cannotReportYourself); } @@ -70,17 +69,18 @@ export default define(meta, async (ps, me) => { throw new ApiError(meta.errors.cannotReportAdmin); } - const report = await AbuseUserReport.insert({ + const report = await AbuseUserReports.save({ + id: genId(), createdAt: new Date(), - userId: user._id, - reporterId: me._id, + userId: user.id, + reporterId: me.id, comment: ps.comment }); // Publish event to moderators setTimeout(async () => { - const moderators = await User.find({ - $or: [{ + const moderators = await Users.find({ + where: [{ isAdmin: true }, { isModerator: true @@ -88,8 +88,8 @@ export default define(meta, async (ps, me) => { }); for (const moderator of moderators) { - publishAdminStream(moderator._id, 'newAbuseUserReport', { - id: report._id, + publishAdminStream(moderator.id, 'newAbuseUserReport', { + id: report.id, userId: report.userId, reporterId: report.reporterId, comment: report.comment diff --git a/src/server/api/endpoints/users/search.ts b/src/server/api/endpoints/users/search.ts index a95f6df6de..2e76546ade 100644 --- a/src/server/api/endpoints/users/search.ts +++ b/src/server/api/endpoints/users/search.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; -import * as escapeRegexp from 'escape-regexp'; -import User, { pack, validateUsername, IUser } from '../../../../models/user'; import define from '../../define'; +import { Users } from '../../../../models'; +import { User } from '../../../../models/entities/user'; export const meta = { desc: { @@ -62,34 +62,30 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const isUsername = validateUsername(ps.query.replace('@', ''), !ps.localOnly); + const isUsername = Users.validateUsername(ps.query.replace('@', ''), !ps.localOnly); - let users: IUser[] = []; + let users: User[] = []; if (isUsername) { - users = await User - .find({ - host: null, - usernameLower: new RegExp('^' + escapeRegexp(ps.query.replace('@', '').toLowerCase())), - isSuspended: { $ne: true } - }, { - limit: ps.limit, - skip: ps.offset - }); + users = await Users.createQueryBuilder('user') + .where('user.host IS NULL') + .where('user.isSuspended = FALSE') + .where('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) + .take(ps.limit) + .skip(ps.offset) + .getMany(); if (users.length < ps.limit && !ps.localOnly) { - const otherUsers = await User - .find({ - host: { $ne: null }, - usernameLower: new RegExp('^' + escapeRegexp(ps.query.replace('@', '').toLowerCase())), - isSuspended: { $ne: true } - }, { - limit: ps.limit - users.length - }); + const otherUsers = await Users.createQueryBuilder('user') + .where('user.host IS NOT NULL') + .where('user.isSuspended = FALSE') + .where('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) + .take(ps.limit - users.length) + .getMany(); users = users.concat(otherUsers); } } - return await Promise.all(users.map(user => pack(user, me, { detail: ps.detail }))); + return await Users.packMany(users, me, { detail: ps.detail }); }); diff --git a/src/server/api/endpoints/users/show.ts b/src/server/api/endpoints/users/show.ts index 4e59945eba..a605eaf30a 100644 --- a/src/server/api/endpoints/users/show.ts +++ b/src/server/api/endpoints/users/show.ts @@ -1,12 +1,11 @@ import $ from 'cafy'; -import ID, { transform, transformMany } from '../../../../misc/cafy-id'; -import User, { pack, isRemoteUser } from '../../../../models/user'; import resolveRemoteUser from '../../../../remote/resolve-user'; import define from '../../define'; import { apiLogger } from '../../logger'; import { ApiError } from '../../error'; - -const cursorOption = { fields: { data: false } }; +import { ID } from '../../../../misc/cafy-id'; +import { Users } from '../../../../models'; +import { In } from 'typeorm'; export const meta = { desc: { @@ -20,7 +19,6 @@ export const meta = { params: { userId: { validator: $.optional.type(ID), - transform: transform, desc: { 'ja-JP': '対象のユーザーのID', 'en-US': 'Target user ID' @@ -29,7 +27,6 @@ export const meta = { userIds: { validator: $.optional.arr($.type(ID)).unique(), - transform: transformMany, desc: { 'ja-JP': 'ユーザーID (配列)' } @@ -68,42 +65,40 @@ export default define(meta, async (ps, me) => { let user; if (ps.userIds) { - const users = await User.find({ - _id: { - $in: ps.userIds - } + const users = await Users.find({ + id: In(ps.userIds) }); - return await Promise.all(users.map(u => pack(u, me, { + return await Promise.all(users.map(u => Users.pack(u, me, { detail: true }))); } else { // Lookup user if (typeof ps.host === 'string') { - user = await resolveRemoteUser(ps.username, ps.host, cursorOption).catch(e => { + user = await resolveRemoteUser(ps.username, ps.host).catch(e => { apiLogger.warn(`failed to resolve remote user: ${e}`); throw new ApiError(meta.errors.failedToResolveRemoteUser); }); } else { const q: any = ps.userId != null - ? { _id: ps.userId } + ? { id: ps.userId } : { usernameLower: ps.username.toLowerCase(), host: null }; - user = await User.findOne(q, cursorOption); + user = await Users.findOne(q); } - if (user === null) { + if (user == null) { throw new ApiError(meta.errors.noSuchUser); } // ユーザー情報更新 - if (isRemoteUser(user)) { + if (Users.isRemoteUser(user)) { if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { resolveRemoteUser(ps.username, ps.host, { }, true); } } - return await pack(user, me, { + return await Users.pack(user, me, { detail: true }); } diff --git a/src/server/api/index.ts b/src/server/api/index.ts index fac57ca06e..7858efd927 100644 --- a/src/server/api/index.ts +++ b/src/server/api/index.ts @@ -15,8 +15,8 @@ import signin from './private/signin'; import discord from './service/discord'; import github from './service/github'; import twitter from './service/twitter'; -import Instance from '../../models/instance'; import { toASCII } from 'punycode'; +import { Instances } from '../../models'; // Init app const app = new Koa(); @@ -67,10 +67,9 @@ router.use(github.routes()); router.use(twitter.routes()); router.get('/v1/instance/peers', async ctx => { - const instances = await Instance.find({ - }, { - host: 1 - }); + const instances = await Instances.find({ + select: ['host'] + }); const punyCodes = instances.map(instance => toASCII(instance.host)); diff --git a/src/server/api/limiter.ts b/src/server/api/limiter.ts index 3d66172fd8..e29c061337 100644 --- a/src/server/api/limiter.ts +++ b/src/server/api/limiter.ts @@ -2,12 +2,12 @@ import * as Limiter from 'ratelimiter'; import limiterDB from '../../db/redis'; import { IEndpoint } from './endpoints'; import getAcct from '../../misc/acct/render'; -import { IUser } from '../../models/user'; +import { User } from '../../models/entities/user'; import Logger from '../../services/logger'; const logger = new Logger('limiter'); -export default (endpoint: IEndpoint, user: IUser) => new Promise((ok, reject) => { +export default (endpoint: IEndpoint, user: User) => new Promise((ok, reject) => { // Redisがインストールされてない場合は常に許可 if (limiterDB == null) { ok(); @@ -38,7 +38,7 @@ export default (endpoint: IEndpoint, user: IUser) => new Promise((ok, reject) => // Short-term limit function min() { const minIntervalLimiter = new Limiter({ - id: `${user._id}:${key}:min`, + id: `${user.id}:${key}:min`, duration: limitation.minInterval, max: 1, db: limiterDB @@ -66,7 +66,7 @@ export default (endpoint: IEndpoint, user: IUser) => new Promise((ok, reject) => // Long term limit function max() { const limiter = new Limiter({ - id: `${user._id}:${key}`, + id: `${user.id}:${key}`, duration: limitation.duration, max: limitation.max, db: limiterDB diff --git a/src/server/api/openapi/schemas.ts b/src/server/api/openapi/schemas.ts index 70a0d6faf0..5992fee835 100644 --- a/src/server/api/openapi/schemas.ts +++ b/src/server/api/openapi/schemas.ts @@ -221,7 +221,7 @@ export const schemas = { }, type: { type: 'string', - enum: ['follow', 'receiveFollowRequest', 'mention', 'reply', 'renote', 'quote', 'reaction', 'poll_vote'], + enum: ['follow', 'receiveFollowRequest', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote'], description: 'The type of the notification.' }, }, @@ -258,7 +258,7 @@ export const schemas = { description: 'The MD5 hash of this Drive file.', example: '15eca7fba0480996e2245f5185bf39f2' }, - datasize: { + size: { type: 'number', description: 'The size of this Drive file. (bytes)', example: 51469 @@ -275,7 +275,7 @@ export const schemas = { description: 'Whether this Drive file is sensitive.', }, }, - required: ['id', 'createdAt', 'name', 'type', 'datasize', 'md5'] + required: ['id', 'createdAt', 'name', 'type', 'size', 'md5'] }, DriveFolder: { @@ -318,6 +318,40 @@ export const schemas = { required: ['id', 'createdAt', 'name'] }, + Following: { + type: 'object', + properties: { + id: { + type: 'string', + format: 'id', + description: 'The unique identifier for this following.', + example: 'xxxxxxxxxxxxxxxxxxxxxxxx', + }, + createdAt: { + type: 'string', + format: 'date-time', + description: 'The date that the following was created.' + }, + followeeId: { + type: 'string', + format: 'id', + }, + followee: { + $ref: '#/components/schemas/User', + description: 'The followee.' + }, + followerId: { + type: 'string', + format: 'id', + }, + follower: { + $ref: '#/components/schemas/User', + description: 'The follower.' + }, + }, + required: ['id', 'createdAt', 'followeeId', 'followerId'] + }, + Muting: { type: 'object', properties: { diff --git a/src/server/api/private/signin.ts b/src/server/api/private/signin.ts index 40bcd2c5d6..c1fd908d8a 100644 --- a/src/server/api/private/signin.ts +++ b/src/server/api/private/signin.ts @@ -1,11 +1,12 @@ import * as Koa from 'koa'; import * as bcrypt from 'bcryptjs'; import * as speakeasy from 'speakeasy'; -import User, { ILocalUser } from '../../../models/user'; -import Signin, { pack } from '../../../models/signin'; import { publishMainStream } from '../../../services/stream'; import signin from '../common/signin'; import config from '../../../config'; +import { Users, Signins } from '../../../models'; +import { ILocalUser } from '../../../models/entities/user'; +import { genId } from '../../../misc/gen-id'; export default async (ctx: Koa.BaseContext) => { ctx.set('Access-Control-Allow-Origin', config.url); @@ -32,17 +33,12 @@ export default async (ctx: Koa.BaseContext) => { } // Fetch user - const user = await User.findOne({ + const user = await Users.findOne({ usernameLower: username.toLowerCase(), host: null - }, { - fields: { - data: false, - profile: false - } - }) as ILocalUser; + }) as ILocalUser; - if (user === null) { + if (user == null) { ctx.throw(404, { error: 'user not found' }); @@ -77,14 +73,15 @@ export default async (ctx: Koa.BaseContext) => { } // Append signin history - const record = await Signin.insert({ + const record = await Signins.save({ + id: genId(), createdAt: new Date(), - userId: user._id, + userId: user.id, ip: ctx.ip, headers: ctx.headers, success: same }); // Publish signin event - publishMainStream(user._id, 'signin', await pack(record)); + publishMainStream(user.id, 'signin', await Signins.pack(record)); }; diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index 89b7b330d2..1d304b8e11 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -1,14 +1,15 @@ import * as Koa from 'koa'; import * as bcrypt from 'bcryptjs'; -import { generate as generateKeypair } from '../../../crypto_key'; -import User, { IUser, validateUsername, validatePassword, pack } from '../../../models/user'; +import { generateKeyPair } from 'crypto'; import generateUserToken from '../common/generate-native-user-token'; import config from '../../../config'; -import Meta from '../../../models/meta'; -import RegistrationTicket from '../../../models/registration-tickets'; -import usersChart from '../../../services/chart/users'; import fetchMeta from '../../../misc/fetch-meta'; import * as recaptcha from 'recaptcha-promise'; +import { Users, RegistrationTickets, UserServiceLinkings, UserKeypairs } from '../../../models'; +import { genId } from '../../../misc/gen-id'; +import { usersChart } from '../../../services/chart'; +import { UserServiceLinking } from '../../../models/entities/user-service-linking'; +import { User } from '../../../models/entities/user'; export default async (ctx: Koa.BaseContext) => { const body = ctx.request.body as any; @@ -32,6 +33,7 @@ export default async (ctx: Koa.BaseContext) => { const username = body['username']; const password = body['password']; + const host = process.env.NODE_ENV === 'test' ? (body['host'] || null) : null; const invitationCode = body['invitationCode']; if (instance && instance.disableRegistration) { @@ -40,7 +42,7 @@ export default async (ctx: Koa.BaseContext) => { return; } - const ticket = await RegistrationTicket.findOne({ + const ticket = await RegistrationTickets.findOne({ code: invitationCode }); @@ -49,39 +51,22 @@ export default async (ctx: Koa.BaseContext) => { return; } - RegistrationTicket.remove({ - _id: ticket._id - }); + RegistrationTickets.delete(ticket.id); } // Validate username - if (!validateUsername(username)) { + if (!Users.validateUsername(username)) { ctx.status = 400; return; } // Validate password - if (!validatePassword(password)) { + if (!Users.validatePassword(password)) { ctx.status = 400; return; } - const usersCount = await User.count({}); - - // Fetch exist user that same username - const usernameExist = await User - .count({ - usernameLower: username.toLowerCase(), - host: null - }, { - limit: 1 - }); - - // Check username already used - if (usernameExist !== 0) { - ctx.status = 400; - return; - } + const usersCount = await Users.count({}); // Generate hash of password const salt = await bcrypt.genSalt(8); @@ -90,46 +75,50 @@ export default async (ctx: Koa.BaseContext) => { // Generate secret const secret = generateUserToken(); - // Create account - const account: IUser = await User.insert({ - avatarId: null, - bannerId: null, + if (await Users.findOne({ usernameLower: username.toLowerCase(), host: null })) { + ctx.status = 400; + return; + } + + const account = await Users.save({ + id: genId(), createdAt: new Date(), - description: null, - followersCount: 0, - followingCount: 0, - name: null, - notesCount: 0, username: username, usernameLower: username.toLowerCase(), - host: null, - keypair: generateKeypair(), + host: host, token: secret, password: hash, isAdmin: config.autoAdmin && usersCount === 0, autoAcceptFollowed: true, - profile: { - bio: null, - birthday: null, - location: null - }, - settings: { - autoWatch: false - } + autoWatch: false + } as User); + + await UserKeypairs.save({ + id: genId(), + keyPem: await new Promise<string>((s, j) => generateKeyPair('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem', + cipher: undefined, + passphrase: undefined + } + }, (e, _, x) => e ? j(e) : s(x))), + userId: account.id }); - //#region Increment users count - Meta.update({}, { - $inc: { - 'stats.usersCount': 1, - 'stats.originalUsersCount': 1 - } - }, { upsert: true }); - //#endregion + await UserServiceLinkings.save({ + id: genId(), + userId: account.id + } as UserServiceLinking); usersChart.update(account, true); - const res = await pack(account, account, { + const res = await Users.pack(account, account, { detail: true, includeSecrets: true }); diff --git a/src/server/api/service/discord.ts b/src/server/api/service/discord.ts index 92f5bbf72d..4290e1ff9d 100644 --- a/src/server/api/service/discord.ts +++ b/src/server/api/service/discord.ts @@ -2,13 +2,14 @@ import * as Koa from 'koa'; import * as Router from 'koa-router'; import * as request from 'request'; import { OAuth2 } from 'oauth'; -import User, { pack, ILocalUser } from '../../../models/user'; import config from '../../../config'; import { publishMainStream } from '../../../services/stream'; import redis from '../../../db/redis'; import * as uuid from 'uuid'; import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; +import { Users, UserServiceLinkings } from '../../../models'; +import { ILocalUser } from '../../../models/entities/user'; function getUserToken(ctx: Koa.BaseContext) { return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1]; @@ -39,19 +40,27 @@ router.get('/disconnect/discord', async ctx => { return; } - const user = await User.findOneAndUpdate({ + const user = await Users.findOne({ host: null, - 'token': userToken + token: userToken + }); + + await UserServiceLinkings.update({ + userId: user.id }, { - $set: { - 'discord': null - } + discord: false, + discordAccessToken: null, + discordRefreshToken: null, + discordExpiresDate: null, + discordId: null, + discordUsername: null, + discordDiscriminator: null, }); ctx.body = `Discordの連携を解除しました :v:`; // Publish i updated event - publishMainStream(user._id, 'meUpdated', await pack(user, user, { + publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { detail: true, includeSecrets: true })); @@ -193,32 +202,30 @@ router.get('/dc/cb', async ctx => { return; } - let user = await User.findOne({ - host: null, - 'discord.id': id - }) as ILocalUser; + const link = await UserServiceLinkings.createQueryBuilder() + .where('discord @> :discord', { + discord: { + id: id, + }, + }) + .andWhere('userHost IS NULL') + .getOne(); - if (!user) { + if (link == null) { ctx.throw(404, `@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`); return; } - user = await User.findOneAndUpdate({ - host: null, - 'discord.id': id - }, { - $set: { - discord: { - accessToken, - refreshToken, - expiresDate, - username, - discriminator - } - } - }) as ILocalUser; + await UserServiceLinkings.update(link.id, { + discord: true, + discordAccessToken: accessToken, + discordRefreshToken: refreshToken, + discordExpiresDate: expiresDate, + discordUsername: username, + discordDiscriminator: discriminator + }); - signin(ctx, user, true); + signin(ctx, await Users.findOne(link.userId) as ILocalUser, true); } else { const code = ctx.query.code; @@ -277,26 +284,25 @@ router.get('/dc/cb', async ctx => { return; } - const user = await User.findOneAndUpdate({ + const user = await Users.findOne({ host: null, token: userToken - }, { - $set: { - discord: { - accessToken, - refreshToken, - expiresDate, - id, - username, - discriminator - } - } + }); + + await UserServiceLinkings.update({ userId: user.id }, { + discord: true, + discordAccessToken: accessToken, + discordRefreshToken: refreshToken, + discordExpiresDate: expiresDate, + discordId: id, + discordUsername: username, + discordDiscriminator: discriminator }); ctx.body = `Discord: @${username}#${discriminator} を、Misskey: @${user.username} に接続しました!`; // Publish i updated event - publishMainStream(user._id, 'meUpdated', await pack(user, user, { + publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { detail: true, includeSecrets: true })); diff --git a/src/server/api/service/github.ts b/src/server/api/service/github.ts index cf3589a4b7..e59b149d19 100644 --- a/src/server/api/service/github.ts +++ b/src/server/api/service/github.ts @@ -2,13 +2,14 @@ import * as Koa from 'koa'; import * as Router from 'koa-router'; import * as request from 'request'; import { OAuth2 } from 'oauth'; -import User, { pack, ILocalUser } from '../../../models/user'; import config from '../../../config'; import { publishMainStream } from '../../../services/stream'; import redis from '../../../db/redis'; import * as uuid from 'uuid'; import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; +import { Users, UserServiceLinkings } from '../../../models'; +import { ILocalUser } from '../../../models/entities/user'; function getUserToken(ctx: Koa.BaseContext) { return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1]; @@ -39,19 +40,24 @@ router.get('/disconnect/github', async ctx => { return; } - const user = await User.findOneAndUpdate({ + const user = await Users.findOne({ host: null, - 'token': userToken + token: userToken + }); + + await UserServiceLinkings.update({ + userId: user.id }, { - $set: { - 'github': null - } + github: false, + githubAccessToken: null, + githubId: null, + githubLogin: null, }); ctx.body = `GitHubの連携を解除しました :v:`; // Publish i updated event - publishMainStream(user._id, 'meUpdated', await pack(user, user, { + publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { detail: true, includeSecrets: true })); @@ -185,17 +191,21 @@ router.get('/gh/cb', async ctx => { return; } - const user = await User.findOne({ - host: null, - 'github.id': id - }) as ILocalUser; + const link = await UserServiceLinkings.createQueryBuilder() + .where('github @> :github', { + github: { + id: id, + }, + }) + .andWhere('userHost IS NULL') + .getOne(); - if (!user) { + if (link == null) { ctx.throw(404, `@${login}と連携しているMisskeyアカウントはありませんでした...`); return; } - signin(ctx, user, true); + signin(ctx, await Users.findOne(link.userId) as ILocalUser, true); } else { const code = ctx.query.code; @@ -248,23 +258,22 @@ router.get('/gh/cb', async ctx => { return; } - const user = await User.findOneAndUpdate({ + const user = await Users.findOne({ host: null, token: userToken - }, { - $set: { - github: { - accessToken, - id, - login - } - } + }); + + await UserServiceLinkings.update({ userId: user.id }, { + github: true, + githubAccessToken: accessToken, + githubId: id, + githubLogin: login, }); ctx.body = `GitHub: @${login} を、Misskey: @${user.username} に接続しました!`; // Publish i updated event - publishMainStream(user._id, 'meUpdated', await pack(user, user, { + publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { detail: true, includeSecrets: true })); diff --git a/src/server/api/service/twitter.ts b/src/server/api/service/twitter.ts index fc23808e21..77cf71395b 100644 --- a/src/server/api/service/twitter.ts +++ b/src/server/api/service/twitter.ts @@ -3,11 +3,12 @@ import * as Router from 'koa-router'; import * as uuid from 'uuid'; import autwh from 'autwh'; import redis from '../../../db/redis'; -import User, { pack, ILocalUser } from '../../../models/user'; import { publishMainStream } from '../../../services/stream'; import config from '../../../config'; import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; +import { Users, UserServiceLinkings } from '../../../models'; +import { ILocalUser } from '../../../models/entities/user'; function getUserToken(ctx: Koa.BaseContext) { return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1]; @@ -38,19 +39,25 @@ router.get('/disconnect/twitter', async ctx => { return; } - const user = await User.findOneAndUpdate({ + const user = await Users.findOne({ host: null, - 'token': userToken + token: userToken + }); + + await UserServiceLinkings.update({ + userId: user.id }, { - $set: { - 'twitter': null - } + twitter: false, + twitterAccessToken: null, + twitterAccessTokenSecret: null, + twitterUserId: null, + twitterScreenName: null, }); ctx.body = `Twitterの連携を解除しました :v:`; // Publish i updated event - publishMainStream(user._id, 'meUpdated', await pack(user, user, { + publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { detail: true, includeSecrets: true })); @@ -132,17 +139,21 @@ router.get('/tw/cb', async ctx => { const result = await twAuth.done(JSON.parse(twCtx), ctx.query.oauth_verifier); - const user = await User.findOne({ - host: null, - 'twitter.userId': result.userId - }) as ILocalUser; + const link = await UserServiceLinkings.createQueryBuilder() + .where('twitter @> :twitter', { + twitter: { + userId: result.userId, + }, + }) + .andWhere('userHost IS NULL') + .getOne(); - if (user == null) { + if (link == null) { ctx.throw(404, `@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`); return; } - signin(ctx, user, true); + signin(ctx, await Users.findOne(link.userId) as ILocalUser, true); } else { const verifier = ctx.query.oauth_verifier; @@ -161,24 +172,23 @@ router.get('/tw/cb', async ctx => { const result = await twAuth.done(JSON.parse(twCtx), verifier); - const user = await User.findOneAndUpdate({ + const user = await Users.findOne({ host: null, token: userToken - }, { - $set: { - twitter: { - accessToken: result.accessToken, - accessTokenSecret: result.accessTokenSecret, - userId: result.userId, - screenName: result.screenName - } - } + }); + + await UserServiceLinkings.update({ userId: user.id }, { + twitter: true, + twitterAccessToken: result.accessToken, + twitterAccessTokenSecret: result.accessTokenSecret, + twitterUserId: result.userId, + twitterScreenName: result.screenName, }); ctx.body = `Twitter: @${result.screenName} を、Misskey: @${user.username} に接続しました!`; // Publish i updated event - publishMainStream(user._id, 'meUpdated', await pack(user, user, { + publishMainStream(user.id, 'meUpdated', await Users.pack(user, user, { detail: true, includeSecrets: true })); diff --git a/src/server/api/stream/channel.ts b/src/server/api/stream/channel.ts index bdbe4605cf..18fa651820 100644 --- a/src/server/api/stream/channel.ts +++ b/src/server/api/stream/channel.ts @@ -15,6 +15,14 @@ export default abstract class Channel { return this.connection.user; } + protected get following() { + return this.connection.following; + } + + protected get muting() { + return this.connection.muting; + } + protected get subscriber() { return this.connection.subscriber; } diff --git a/src/server/api/stream/channels/admin.ts b/src/server/api/stream/channels/admin.ts index 6bcd1a7e0b..e2eba10f78 100644 --- a/src/server/api/stream/channels/admin.ts +++ b/src/server/api/stream/channels/admin.ts @@ -9,7 +9,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe admin stream - this.subscriber.on(`adminStream:${this.user._id}`, data => { + this.subscriber.on(`adminStream:${this.user.id}`, data => { this.send(data); }); } diff --git a/src/server/api/stream/channels/drive.ts b/src/server/api/stream/channels/drive.ts index 391c4b5c32..671aad4366 100644 --- a/src/server/api/stream/channels/drive.ts +++ b/src/server/api/stream/channels/drive.ts @@ -9,7 +9,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe drive stream - this.subscriber.on(`driveStream:${this.user._id}`, data => { + this.subscriber.on(`driveStream:${this.user.id}`, data => { this.send(data); }); } diff --git a/src/server/api/stream/channels/games/reversi-game.ts b/src/server/api/stream/channels/games/reversi-game.ts index 87df9e194c..158f108c4e 100644 --- a/src/server/api/stream/channels/games/reversi-game.ts +++ b/src/server/api/stream/channels/games/reversi-game.ts @@ -1,22 +1,22 @@ import autobind from 'autobind-decorator'; import * as CRC32 from 'crc-32'; -import * as mongo from 'mongodb'; -import ReversiGame, { pack } from '../../../../../models/games/reversi/game'; import { publishReversiGameStream } from '../../../../../services/stream'; import Reversi from '../../../../../games/reversi/core'; import * as maps from '../../../../../games/reversi/maps'; import Channel from '../../channel'; +import { ReversiGame } from '../../../../../models/entities/games/reversi/game'; +import { ReversiGames } from '../../../../../models'; export default class extends Channel { public readonly chName = 'gamesReversiGame'; public static shouldShare = false; public static requireCredential = false; - private gameId: mongo.ObjectID; + private gameId: ReversiGame['id']; @autobind public async init(params: any) { - this.gameId = new mongo.ObjectID(params.gameId as string); + this.gameId = params.gameId; // Subscribe game stream this.subscriber.on(`reversiGameStream:${this.gameId}`, data => { @@ -29,7 +29,7 @@ export default class extends Channel { switch (type) { case 'accept': this.accept(true); break; case 'cancelAccept': this.accept(false); break; - case 'updateSettings': this.updateSettings(body.settings); break; + case 'updateSettings': this.updateSettings(body.key, body.value); break; case 'initForm': this.initForm(body); break; case 'updateForm': this.updateForm(body.id, body.value); break; case 'message': this.message(body); break; @@ -39,54 +39,55 @@ export default class extends Channel { } @autobind - private async updateSettings(settings: any) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + private async updateSettings(key: string, value: any) { + const game = await ReversiGames.findOne(this.gameId); if (game.isStarted) return; - if (!game.user1Id.equals(this.user._id) && !game.user2Id.equals(this.user._id)) return; - if (game.user1Id.equals(this.user._id) && game.user1Accepted) return; - if (game.user2Id.equals(this.user._id) && game.user2Accepted) return; + if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; + if ((game.user1Id === this.user.id) && game.user1Accepted) return; + if ((game.user2Id === this.user.id) && game.user2Accepted) return; - await ReversiGame.update({ _id: this.gameId }, { - $set: { - settings - } + if (!['map', 'bw', 'isLlotheo', 'canPutEverywhere', 'loopedBoard'].includes(key)) return; + + await ReversiGames.update({ id: this.gameId }, { + [key]: value }); - publishReversiGameStream(this.gameId, 'updateSettings', settings); + publishReversiGameStream(this.gameId, 'updateSettings', { + key: key, + value: value + }); } @autobind private async initForm(form: any) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + const game = await ReversiGames.findOne(this.gameId); if (game.isStarted) return; - if (!game.user1Id.equals(this.user._id) && !game.user2Id.equals(this.user._id)) return; + if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - const set = game.user1Id.equals(this.user._id) ? { + const set = game.user1Id === this.user.id ? { form1: form } : { - form2: form - }; + form2: form + }; - await ReversiGame.update({ _id: this.gameId }, { - $set: set - }); + await ReversiGames.update({ id: this.gameId }, set); publishReversiGameStream(this.gameId, 'initForm', { - userId: this.user._id, + userId: this.user.id, form }); } @autobind private async updateForm(id: string, value: any) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + const game = await ReversiGames.findOne({ id: this.gameId }); if (game.isStarted) return; - if (!game.user1Id.equals(this.user._id) && !game.user2Id.equals(this.user._id)) return; + if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - const form = game.user1Id.equals(this.user._id) ? game.form2 : game.form1; + const form = game.user1Id === this.user.id ? game.form2 : game.form1; const item = form.find((i: any) => i.id == id); @@ -94,18 +95,16 @@ export default class extends Channel { item.value = value; - const set = game.user1Id.equals(this.user._id) ? { + const set = game.user1Id === this.user.id ? { form2: form } : { form1: form }; - await ReversiGame.update({ _id: this.gameId }, { - $set: set - }); + await ReversiGames.update({ id: this.gameId }, set); publishReversiGameStream(this.gameId, 'updateForm', { - userId: this.user._id, + userId: this.user.id, id, value }); @@ -115,24 +114,22 @@ export default class extends Channel { private async message(message: any) { message.id = Math.random(); publishReversiGameStream(this.gameId, 'message', { - userId: this.user._id, + userId: this.user.id, message }); } @autobind private async accept(accept: boolean) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + const game = await ReversiGames.findOne(this.gameId); if (game.isStarted) return; let bothAccepted = false; - if (game.user1Id.equals(this.user._id)) { - await ReversiGame.update({ _id: this.gameId }, { - $set: { - user1Accepted: accept - } + if (game.user1Id === this.user.id) { + await ReversiGames.update({ id: this.gameId }, { + user1Accepted: accept }); publishReversiGameStream(this.gameId, 'changeAccepts', { @@ -141,11 +138,9 @@ export default class extends Channel { }); if (accept && game.user2Accepted) bothAccepted = true; - } else if (game.user2Id.equals(this.user._id)) { - await ReversiGame.update({ _id: this.gameId }, { - $set: { - user2Accepted: accept - } + } else if (game.user2Id === this.user.id) { + await ReversiGames.update({ id: this.gameId }, { + user2Accepted: accept }); publishReversiGameStream(this.gameId, 'changeAccepts', { @@ -161,15 +156,15 @@ export default class extends Channel { if (bothAccepted) { // 3秒後、まだacceptされていたらゲーム開始 setTimeout(async () => { - const freshGame = await ReversiGame.findOne({ _id: this.gameId }); + const freshGame = await ReversiGames.findOne(this.gameId); if (freshGame == null || freshGame.isStarted || freshGame.isEnded) return; if (!freshGame.user1Accepted || !freshGame.user2Accepted) return; let bw: number; - if (freshGame.settings.bw == 'random') { + if (freshGame.bw == 'random') { bw = Math.random() > 0.5 ? 1 : 2; } else { - bw = freshGame.settings.bw as number; + bw = parseInt(freshGame.bw, 10); } function getRandomMap() { @@ -178,22 +173,20 @@ export default class extends Channel { return Object.values(maps)[rnd].data; } - const map = freshGame.settings.map != null ? freshGame.settings.map : getRandomMap(); + const map = freshGame.map != null ? freshGame.map : getRandomMap(); - await ReversiGame.update({ _id: this.gameId }, { - $set: { - startedAt: new Date(), - isStarted: true, - black: bw, - 'settings.map': map - } + await ReversiGames.update({ id: this.gameId }, { + startedAt: new Date(), + isStarted: true, + black: bw, + map: map }); //#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理 const o = new Reversi(map, { - isLlotheo: freshGame.settings.isLlotheo, - canPutEverywhere: freshGame.settings.canPutEverywhere, - loopedBoard: freshGame.settings.loopedBoard + isLlotheo: freshGame.isLlotheo, + canPutEverywhere: freshGame.canPutEverywhere, + loopedBoard: freshGame.loopedBoard }); if (o.isEnded) { @@ -206,23 +199,22 @@ export default class extends Channel { winner = null; } - await ReversiGame.update({ - _id: this.gameId + await ReversiGames.update({ + id: this.gameId }, { - $set: { - isEnded: true, - winnerId: winner - } - }); + isEnded: true, + winnerId: winner + }); publishReversiGameStream(this.gameId, 'ended', { winnerId: winner, - game: await pack(this.gameId, this.user) + game: await ReversiGames.pack(this.gameId, this.user) }); } //#endregion - publishReversiGameStream(this.gameId, 'started', await pack(this.gameId, this.user)); + publishReversiGameStream(this.gameId, 'started', + await ReversiGames.pack(this.gameId, this.user)); }, 3000); } } @@ -230,16 +222,16 @@ export default class extends Channel { // 石を打つ @autobind private async set(pos: number) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + const game = await ReversiGames.findOne(this.gameId); if (!game.isStarted) return; if (game.isEnded) return; - if (!game.user1Id.equals(this.user._id) && !game.user2Id.equals(this.user._id)) return; + if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; - const o = new Reversi(game.settings.map, { - isLlotheo: game.settings.isLlotheo, - canPutEverywhere: game.settings.canPutEverywhere, - loopedBoard: game.settings.loopedBoard + const o = new Reversi(game.map, { + isLlotheo: game.isLlotheo, + canPutEverywhere: game.canPutEverywhere, + loopedBoard: game.loopedBoard }); for (const log of game.logs) { @@ -247,7 +239,7 @@ export default class extends Channel { } const myColor = - (game.user1Id.equals(this.user._id) && game.black == 1) || (game.user2Id.equals(this.user._id) && game.black == 2) + ((game.user1Id === this.user.id) && game.black == 1) || ((game.user2Id === this.user.id) && game.black == 2) ? true : false; @@ -271,20 +263,18 @@ export default class extends Channel { pos }; - const crc32 = CRC32.str(game.logs.map(x => x.pos.toString()).join('') + pos.toString()); + const crc32 = CRC32.str(game.logs.map(x => x.pos.toString()).join('') + pos.toString()).toString(); - await ReversiGame.update({ - _id: this.gameId + game.logs.push(log); + + await ReversiGames.update({ + id: this.gameId }, { - $set: { - crc32, - isEnded: o.isEnded, - winnerId: winner - }, - $push: { - logs: log - } - }); + crc32, + isEnded: o.isEnded, + winnerId: winner, + logs: game.logs + }); publishReversiGameStream(this.gameId, 'set', Object.assign(log, { next: o.turn @@ -293,14 +283,14 @@ export default class extends Channel { if (o.isEnded) { publishReversiGameStream(this.gameId, 'ended', { winnerId: winner, - game: await pack(this.gameId, this.user) + game: await ReversiGames.pack(this.gameId, this.user) }); } } @autobind private async check(crc32: string) { - const game = await ReversiGame.findOne({ _id: this.gameId }); + const game = await ReversiGames.findOne(this.gameId); if (!game.isStarted) return; @@ -308,7 +298,7 @@ export default class extends Channel { if (game.crc32 == null) return; if (crc32 !== game.crc32) { - this.send('rescue', await pack(game, this.user)); + this.send('rescue', await ReversiGames.pack(game, this.user)); } } } diff --git a/src/server/api/stream/channels/games/reversi.ts b/src/server/api/stream/channels/games/reversi.ts index 1b1ad187a3..0498e5e017 100644 --- a/src/server/api/stream/channels/games/reversi.ts +++ b/src/server/api/stream/channels/games/reversi.ts @@ -1,8 +1,7 @@ import autobind from 'autobind-decorator'; -import * as mongo from 'mongodb'; -import Matching, { pack } from '../../../../../models/games/reversi/matching'; import { publishMainStream } from '../../../../../services/stream'; import Channel from '../../channel'; +import { ReversiMatchings } from '../../../../../models'; export default class extends Channel { public readonly chName = 'gamesReversi'; @@ -12,7 +11,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe reversi stream - this.subscriber.on(`reversiStream:${this.user._id}`, data => { + this.subscriber.on(`reversiStream:${this.user.id}`, data => { this.send(data); }); } @@ -22,12 +21,12 @@ export default class extends Channel { switch (type) { case 'ping': if (body.id == null) return; - const matching = await Matching.findOne({ - parentId: this.user._id, - childId: new mongo.ObjectID(body.id) + const matching = await ReversiMatchings.findOne({ + parentId: this.user.id, + childId: body.id }); if (matching == null) return; - publishMainStream(matching.childId, 'reversiInvited', await pack(matching, matching.childId)); + publishMainStream(matching.childId, 'reversiInvited', await ReversiMatchings.pack(matching, matching.childId)); break; } } diff --git a/src/server/api/stream/channels/global-timeline.ts b/src/server/api/stream/channels/global-timeline.ts index b3689d47f5..bfb7697ba7 100644 --- a/src/server/api/stream/channels/global-timeline.ts +++ b/src/server/api/stream/channels/global-timeline.ts @@ -1,17 +1,14 @@ import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; import Channel from '../channel'; import fetchMeta from '../../../../misc/fetch-meta'; +import { Notes } from '../../../../models'; export default class extends Channel { public readonly chName = 'globalTimeline'; public static shouldShare = true; public static requireCredential = false; - private mutedUserIds: string[] = []; - @autobind public async init(params: any) { const meta = await fetchMeta(); @@ -20,29 +17,26 @@ export default class extends Channel { } // Subscribe events - this.subscriber.on('globalTimeline', this.onNote); - - const mute = await Mute.find({ muterId: this.user._id }); - this.mutedUserIds = mute.map(m => m.muteeId.toString()); + this.subscriber.on('notesStream', this.onNote); } @autobind private async onNote(note: any) { // リプライなら再pack if (note.replyId != null) { - note.reply = await pack(note.replyId, this.user, { + note.reply = await Notes.pack(note.replyId, this.user, { detail: true }); } // Renoteなら再pack if (note.renoteId != null) { - note.renote = await pack(note.renoteId, this.user, { + note.renote = await Notes.pack(note.renoteId, this.user, { detail: true }); } // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (shouldMuteThisNote(note, this.mutedUserIds)) return; + if (shouldMuteThisNote(note, this.muting)) return; this.send('note', note); } @@ -50,6 +44,6 @@ export default class extends Channel { @autobind public dispose() { // Unsubscribe events - this.subscriber.off('globalTimeline', this.onNote); + this.subscriber.off('notesStream', this.onNote); } } diff --git a/src/server/api/stream/channels/hashtag.ts b/src/server/api/stream/channels/hashtag.ts index 586ce02f06..36c56c7ab6 100644 --- a/src/server/api/stream/channels/hashtag.ts +++ b/src/server/api/stream/channels/hashtag.ts @@ -1,40 +1,46 @@ import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; import Channel from '../channel'; +import { Notes } from '../../../../models'; export default class extends Channel { public readonly chName = 'hashtag'; public static shouldShare = false; public static requireCredential = false; + private q: string[][]; @autobind public async init(params: any) { - const mute = this.user ? await Mute.find({ muterId: this.user._id }) : null; - const mutedUserIds = mute ? mute.map(m => m.muteeId.toString()) : []; + this.q = params.q; - const q: string[][] = params.q; - - if (q == null) return; + if (this.q == null) return; // Subscribe stream - this.subscriber.on('hashtag', async note => { - const noteTags = note.tags.map((t: string) => t.toLowerCase()); - const matched = q.some(tags => tags.every(tag => noteTags.includes(tag.toLowerCase()))); - if (!matched) return; + this.subscriber.on('notesStream', this.onNote); + } + + @autobind + private async onNote(note: any) { + const noteTags = note.tags.map((t: string) => t.toLowerCase()); + const matched = this.q.some(tags => tags.every(tag => noteTags.includes(tag.toLowerCase()))); + if (!matched) return; - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await pack(note.renoteId, this.user, { - detail: true - }); - } + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await Notes.pack(note.renoteId, this.user, { + detail: true + }); + } - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (shouldMuteThisNote(note, mutedUserIds)) return; + // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する + if (shouldMuteThisNote(note, this.muting)) return; - this.send('note', note); - }); + this.send('note', note); + } + + @autobind + public dispose() { + // Unsubscribe events + this.subscriber.off('notesStream', this.onNote); } } diff --git a/src/server/api/stream/channels/home-timeline.ts b/src/server/api/stream/channels/home-timeline.ts index 3c0b238720..2cece0947f 100644 --- a/src/server/api/stream/channels/home-timeline.ts +++ b/src/server/api/stream/channels/home-timeline.ts @@ -1,42 +1,49 @@ import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; import Channel from '../channel'; +import { Notes } from '../../../../models'; export default class extends Channel { public readonly chName = 'homeTimeline'; public static shouldShare = true; public static requireCredential = true; - private mutedUserIds: string[] = []; - @autobind public async init(params: any) { // Subscribe events - this.subscriber.on(`homeTimeline:${this.user._id}`, this.onNote); - - const mute = await Mute.find({ muterId: this.user._id }); - this.mutedUserIds = mute.map(m => m.muteeId.toString()); + this.subscriber.on('notesStream', this.onNote); } @autobind private async onNote(note: any) { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await pack(note.replyId, this.user, { - detail: true - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await pack(note.renoteId, this.user, { + // その投稿のユーザーをフォローしていなかったら弾く + if (this.user.id !== note.userId && !this.following.includes(note.userId)) return; + + if (['followers', 'specified'].includes(note.visibility)) { + note = await Notes.pack(note.id, this.user, { detail: true }); + + if (note.isHidden) { + return; + } + } else { + // リプライなら再pack + if (note.replyId != null) { + note.reply = await Notes.pack(note.replyId, this.user, { + detail: true + }); + } + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await Notes.pack(note.renoteId, this.user, { + detail: true + }); + } } // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (shouldMuteThisNote(note, this.mutedUserIds)) return; + if (shouldMuteThisNote(note, this.muting)) return; this.send('note', note); } @@ -44,6 +51,6 @@ export default class extends Channel { @autobind public dispose() { // Unsubscribe events - this.subscriber.off(`homeTimeline:${this.user._id}`, this.onNote); + this.subscriber.off('notesStream', this.onNote); } } diff --git a/src/server/api/stream/channels/hybrid-timeline.ts b/src/server/api/stream/channels/hybrid-timeline.ts deleted file mode 100644 index 35ef17b56b..0000000000 --- a/src/server/api/stream/channels/hybrid-timeline.ts +++ /dev/null @@ -1,55 +0,0 @@ -import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; -import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; -import Channel from '../channel'; -import fetchMeta from '../../../../misc/fetch-meta'; - -export default class extends Channel { - public readonly chName = 'hybridTimeline'; - public static shouldShare = true; - public static requireCredential = true; - - private mutedUserIds: string[] = []; - - @autobind - public async init(params: any) { - const meta = await fetchMeta(); - if (meta.disableLocalTimeline && !this.user.isAdmin && !this.user.isModerator) return; - - // Subscribe events - this.subscriber.on('hybridTimeline', this.onNewNote); - this.subscriber.on(`hybridTimeline:${this.user._id}`, this.onNewNote); - - const mute = await Mute.find({ muterId: this.user._id }); - this.mutedUserIds = mute.map(m => m.muteeId.toString()); - } - - @autobind - private async onNewNote(note: any) { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await pack(note.replyId, this.user, { - detail: true - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await pack(note.renoteId, this.user, { - detail: true - }); - } - - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (shouldMuteThisNote(note, this.mutedUserIds)) return; - - this.send('note', note); - } - - @autobind - public dispose() { - // Unsubscribe events - this.subscriber.off('hybridTimeline', this.onNewNote); - this.subscriber.off(`hybridTimeline:${this.user._id}`, this.onNewNote); - } -} diff --git a/src/server/api/stream/channels/index.ts b/src/server/api/stream/channels/index.ts index 4527fb1e46..199ab0a809 100644 --- a/src/server/api/stream/channels/index.ts +++ b/src/server/api/stream/channels/index.ts @@ -1,7 +1,7 @@ import main from './main'; import homeTimeline from './home-timeline'; import localTimeline from './local-timeline'; -import hybridTimeline from './hybrid-timeline'; +import socialTimeline from './social-timeline'; import globalTimeline from './global-timeline'; import notesStats from './notes-stats'; import serverStats from './server-stats'; @@ -20,7 +20,7 @@ export default { main, homeTimeline, localTimeline, - hybridTimeline, + socialTimeline, globalTimeline, notesStats, serverStats, diff --git a/src/server/api/stream/channels/local-timeline.ts b/src/server/api/stream/channels/local-timeline.ts index 3402023192..4aec2d66b4 100644 --- a/src/server/api/stream/channels/local-timeline.ts +++ b/src/server/api/stream/channels/local-timeline.ts @@ -1,17 +1,14 @@ import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; -import { pack } from '../../../../models/note'; import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; import Channel from '../channel'; import fetchMeta from '../../../../misc/fetch-meta'; +import { Notes } from '../../../../models'; export default class extends Channel { public readonly chName = 'localTimeline'; public static shouldShare = true; public static requireCredential = false; - private mutedUserIds: string[] = []; - @autobind public async init(params: any) { const meta = await fetchMeta(); @@ -20,29 +17,39 @@ export default class extends Channel { } // Subscribe events - this.subscriber.on('localTimeline', this.onNote); - - const mute = this.user ? await Mute.find({ muterId: this.user._id }) : null; - this.mutedUserIds = mute ? mute.map(m => m.muteeId.toString()) : []; + this.subscriber.on('notesStream', this.onNote); } @autobind private async onNote(note: any) { - // リプライなら再pack - if (note.replyId != null) { - note.reply = await pack(note.replyId, this.user, { - detail: true - }); - } - // Renoteなら再pack - if (note.renoteId != null) { - note.renote = await pack(note.renoteId, this.user, { + if (note.user.host !== null) return; + if (note.visibility === 'home') return; + + if (['followers', 'specified'].includes(note.visibility)) { + note = await Notes.pack(note.id, this.user, { detail: true }); + + if (note.isHidden) { + return; + } + } else { + // リプライなら再pack + if (note.replyId != null) { + note.reply = await Notes.pack(note.replyId, this.user, { + detail: true + }); + } + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await Notes.pack(note.renoteId, this.user, { + detail: true + }); + } } // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (shouldMuteThisNote(note, this.mutedUserIds)) return; + if (shouldMuteThisNote(note, this.muting)) return; this.send('note', note); } @@ -50,6 +57,6 @@ export default class extends Channel { @autobind public dispose() { // Unsubscribe events - this.subscriber.off('localTimeline', this.onNote); + this.subscriber.off('notesStream', this.onNote); } } diff --git a/src/server/api/stream/channels/main.ts b/src/server/api/stream/channels/main.ts index 175d914fa5..0d9bf3149d 100644 --- a/src/server/api/stream/channels/main.ts +++ b/src/server/api/stream/channels/main.ts @@ -1,6 +1,6 @@ import autobind from 'autobind-decorator'; -import Mute from '../../../../models/mute'; import Channel from '../channel'; +import { Mutings } from '../../../../models'; export default class extends Channel { public readonly chName = 'main'; @@ -9,16 +9,15 @@ export default class extends Channel { @autobind public async init(params: any) { - const mute = await Mute.find({ muterId: this.user._id }); - const mutedUserIds = mute.map(m => m.muteeId.toString()); + const mute = await Mutings.find({ muterId: this.user.id }); // Subscribe main stream channel - this.subscriber.on(`mainStream:${this.user._id}`, async data => { + this.subscriber.on(`mainStream:${this.user.id}`, async data => { const { type, body } = data; switch (type) { case 'notification': { - if (mutedUserIds.includes(body.userId)) return; + if (mute.map(m => m.muteeId).includes(body.userId)) return; if (body.note && body.note.isHidden) return; break; } diff --git a/src/server/api/stream/channels/messaging-index.ts b/src/server/api/stream/channels/messaging-index.ts index 148ff7f935..648badc1dc 100644 --- a/src/server/api/stream/channels/messaging-index.ts +++ b/src/server/api/stream/channels/messaging-index.ts @@ -9,7 +9,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe messaging index stream - this.subscriber.on(`messagingIndexStream:${this.user._id}`, data => { + this.subscriber.on(`messagingIndexStream:${this.user.id}`, data => { this.send(data); }); } diff --git a/src/server/api/stream/channels/messaging.ts b/src/server/api/stream/channels/messaging.ts index 0d81b4e45c..b81fbb9d4c 100644 --- a/src/server/api/stream/channels/messaging.ts +++ b/src/server/api/stream/channels/messaging.ts @@ -14,7 +14,7 @@ export default class extends Channel { this.otherpartyId = params.otherparty as string; // Subscribe messaging stream - this.subscriber.on(`messagingStream:${this.user._id}-${this.otherpartyId}`, data => { + this.subscriber.on(`messagingStream:${this.user.id}-${this.otherpartyId}`, data => { this.send(data); }); } @@ -23,7 +23,7 @@ export default class extends Channel { public onMessage(type: string, body: any) { switch (type) { case 'read': - read(this.user._id, this.otherpartyId, body.id); + read(this.user.id, this.otherpartyId, body.id); break; } } diff --git a/src/server/api/stream/channels/social-timeline.ts b/src/server/api/stream/channels/social-timeline.ts new file mode 100644 index 0000000000..1d76eed297 --- /dev/null +++ b/src/server/api/stream/channels/social-timeline.ts @@ -0,0 +1,64 @@ +import autobind from 'autobind-decorator'; +import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; +import Channel from '../channel'; +import fetchMeta from '../../../../misc/fetch-meta'; +import { Notes } from '../../../../models'; + +export default class extends Channel { + public readonly chName = 'socialTimeline'; + public static shouldShare = true; + public static requireCredential = true; + + @autobind + public async init(params: any) { + const meta = await fetchMeta(); + if (meta.disableLocalTimeline && !this.user.isAdmin && !this.user.isModerator) return; + + // Subscribe events + this.subscriber.on('notesStream', this.onNote); + } + + @autobind + private async onNote(note: any) { + // 自分自身の投稿 または その投稿のユーザーをフォローしている または ローカルの投稿 の場合だけ + if (!( + this.user.id === note.userId || + this.following.includes(note.userId) || + note.user.host === null + )) return; + + if (['followers', 'specified'].includes(note.visibility)) { + note = await Notes.pack(note.id, this.user, { + detail: true + }); + + if (note.isHidden) { + return; + } + } else { + // リプライなら再pack + if (note.replyId != null) { + note.reply = await Notes.pack(note.replyId, this.user, { + detail: true + }); + } + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await Notes.pack(note.renoteId, this.user, { + detail: true + }); + } + } + + // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する + if (shouldMuteThisNote(note, this.muting)) return; + + this.send('note', note); + } + + @autobind + public dispose() { + // Unsubscribe events + this.subscriber.off('notesStream', this.onNote); + } +} diff --git a/src/server/api/stream/channels/user-list.ts b/src/server/api/stream/channels/user-list.ts index 5debf41770..f5434b8f08 100644 --- a/src/server/api/stream/channels/user-list.ts +++ b/src/server/api/stream/channels/user-list.ts @@ -1,23 +1,81 @@ import autobind from 'autobind-decorator'; import Channel from '../channel'; -import { pack } from '../../../../models/note'; +import { Notes, UserListJoinings } from '../../../../models'; +import shouldMuteThisNote from '../../../../misc/should-mute-this-note'; +import { User } from '../../../../models/entities/user'; export default class extends Channel { public readonly chName = 'userList'; public static shouldShare = false; public static requireCredential = false; + private listId: string; + public listUsers: User['id'][] = []; + private listUsersClock: NodeJS.Timer; @autobind public async init(params: any) { - const listId = params.listId as string; + this.listId = params.listId as string; // Subscribe stream - this.subscriber.on(`userListStream:${listId}`, async data => { - // 再パック - if (data.type == 'note') data.body = await pack(data.body.id, this.user, { + this.subscriber.on(`userListStream:${this.listId}`, this.send); + + this.subscriber.on('notesStream', this.onNote); + + this.updateListUsers(); + this.listUsersClock = setInterval(this.updateListUsers, 5000); + } + + @autobind + private async updateListUsers() { + const users = await UserListJoinings.find({ + where: { + userListId: this.listId, + }, + select: ['userId'] + }); + + this.listUsers = users.map(x => x.userId); + } + + @autobind + private async onNote(note: any) { + if (!this.listUsers.includes(note.userId)) return; + + if (['followers', 'specified'].includes(note.visibility)) { + note = await Notes.pack(note.id, this.user, { detail: true }); - this.send(data); - }); + + if (note.isHidden) { + return; + } + } else { + // リプライなら再pack + if (note.replyId != null) { + note.reply = await Notes.pack(note.replyId, this.user, { + detail: true + }); + } + // Renoteなら再pack + if (note.renoteId != null) { + note.renote = await Notes.pack(note.renoteId, this.user, { + detail: true + }); + } + } + + // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する + if (shouldMuteThisNote(note, this.muting)) return; + + this.send('note', note); + } + + @autobind + public dispose() { + // Unsubscribe events + this.subscriber.off(`userListStream:${this.listId}`, this.send); + this.subscriber.off('notesStream', this.onNote); + + clearInterval(this.listUsersClock); } } diff --git a/src/server/api/stream/index.ts b/src/server/api/stream/index.ts index 22f7646cb9..abbd91ec81 100644 --- a/src/server/api/stream/index.ts +++ b/src/server/api/stream/index.ts @@ -1,33 +1,35 @@ import autobind from 'autobind-decorator'; import * as websocket from 'websocket'; - -import User, { IUser } from '../../../models/user'; -import readNotification from '../common/read-notification'; +import { readNotification } from '../common/read-notification'; import call from '../call'; -import { IApp } from '../../../models/app'; import readNote from '../../../services/note/read'; - import Channel from './channel'; import channels from './channels'; import { EventEmitter } from 'events'; +import { User } from '../../../models/entities/user'; +import { App } from '../../../models/entities/app'; +import { Users, Followings, Mutings } from '../../../models'; /** * Main stream connection */ export default class Connection { - public user?: IUser; - public app: IApp; + public user?: User; + public following: User['id'][] = []; + public muting: User['id'][] = []; + public app: App; private wsConnection: websocket.connection; public subscriber: EventEmitter; private channels: Channel[] = []; private subscribingNotes: any = {}; - public sendMessageToWsOverride: any = null; // 後方互換性のため + private followingClock: NodeJS.Timer; + private mutingClock: NodeJS.Timer; constructor( wsConnection: websocket.connection, subscriber: EventEmitter, - user: IUser, - app: IApp + user: User, + app: App ) { this.wsConnection = wsConnection; this.user = user; @@ -35,6 +37,14 @@ export default class Connection { this.subscriber = subscriber; this.wsConnection.on('message', this.onWsConnectionMessage); + + if (this.user) { + this.updateFollowing(); + this.followingClock = setInterval(this.updateFollowing, 5000); + + this.updateMuting(); + this.mutingClock = setInterval(this.updateMuting, 5000); + } } /** @@ -64,7 +74,7 @@ export default class Connection { @autobind private async onApiRequest(payload: any) { // 新鮮なデータを利用するためにユーザーをフェッチ - const user = this.user ? await User.findOne({ _id: this.user._id }) : null; + const user = this.user ? await Users.findOne(this.user.id) : null; const endpoint = payload.endpoint || payload.ep; // alias @@ -79,7 +89,7 @@ export default class Connection { @autobind private onReadNotification(payload: any) { if (!payload.id) return; - readNotification(this.user._id, payload.id); + readNotification(this.user.id, [payload.id]); } /** @@ -100,7 +110,7 @@ export default class Connection { } if (payload.read) { - readNote(this.user._id, payload.id); + readNote(this.user.id, payload.id); } } @@ -150,7 +160,6 @@ export default class Connection { */ @autobind public sendMessageToWs(type: string, payload: any) { - if (this.sendMessageToWsOverride) return this.sendMessageToWsOverride(type, payload); // 後方互換性のため this.wsConnection.send(JSON.stringify({ type: type, body: payload @@ -208,6 +217,30 @@ export default class Connection { } } + @autobind + private async updateFollowing() { + const followings = await Followings.find({ + where: { + followerId: this.user.id + }, + select: ['followeeId'] + }); + + this.following = followings.map(x => x.followeeId); + } + + @autobind + private async updateMuting() { + const mutings = await Mutings.find({ + where: { + muterId: this.user.id + }, + select: ['muteeId'] + }); + + this.muting = mutings.map(x => x.muteeId); + } + /** * ストリームが切れたとき */ @@ -216,5 +249,8 @@ export default class Connection { for (const c of this.channels.filter(c => c.dispose)) { c.dispose(); } + + if (this.followingClock) clearInterval(this.followingClock); + if (this.mutingClock) clearInterval(this.mutingClock); } } diff --git a/src/server/api/streaming.ts b/src/server/api/streaming.ts index f8f3c0ff4a..ab66f2b6d9 100644 --- a/src/server/api/streaming.ts +++ b/src/server/api/streaming.ts @@ -48,33 +48,6 @@ module.exports = (server: http.Server) => { const main = new MainStreamConnection(connection, ev, user, app); - // 後方互換性のため - if (request.resourceURL.pathname !== '/streaming') { - main.sendMessageToWsOverride = (type: string, payload: any) => { - if (type == 'channel') { - type = payload.type; - payload = payload.body; - } - if (type.startsWith('api:')) { - type = type.replace('api:', 'api-res:'); - } - connection.send(JSON.stringify({ - type: type, - body: payload - })); - }; - - main.connectChannel(Math.random().toString().substr(2, 8), null, - request.resourceURL.pathname === '/' ? 'homeTimeline' : - request.resourceURL.pathname === '/local-timeline' ? 'localTimeline' : - request.resourceURL.pathname === '/hybrid-timeline' ? 'hybridTimeline' : - request.resourceURL.pathname === '/global-timeline' ? 'globalTimeline' : null); - - if (request.resourceURL.pathname === '/') { - main.connectChannel(Math.random().toString().substr(2, 8), null, 'main'); - } - } - connection.once('close', () => { ev.removeAllListeners(); main.dispose(); diff --git a/src/server/file/index.ts b/src/server/file/index.ts index 973528da33..e3487a2636 100644 --- a/src/server/file/index.ts +++ b/src/server/file/index.ts @@ -33,8 +33,8 @@ router.get('/app-default.jpg', ctx => { ctx.body = file; }); -router.get('/:id', sendDriveFile); -router.get('/:id/*', sendDriveFile); +router.get('/:key', sendDriveFile); +router.get('/:key/*', sendDriveFile); // Register router app.use(router.routes()); diff --git a/src/server/file/send-drive-file.ts b/src/server/file/send-drive-file.ts index e0208f3fab..f9b067b79c 100644 --- a/src/server/file/send-drive-file.ts +++ b/src/server/file/send-drive-file.ts @@ -1,12 +1,10 @@ import * as Koa from 'koa'; import * as send from 'koa-send'; -import * as mongodb from 'mongodb'; import * as rename from 'rename'; -import DriveFile, { getDriveFileBucket } from '../../models/drive-file'; -import DriveFileThumbnail, { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail'; -import DriveFileWebpublic, { getDriveFileWebpublicBucket } from '../../models/drive-file-webpublic'; import { serverLogger } from '..'; import { contentDisposition } from '../../misc/content-disposition'; +import { DriveFiles } from '../../models'; +import { InternalStorage } from '../../services/drive/internal-storage'; const assets = `${__dirname}/../../server/file/assets/`; @@ -16,16 +14,14 @@ const commonReadableHandlerGenerator = (ctx: Koa.BaseContext) => (e: Error): voi }; export default async function(ctx: Koa.BaseContext) { - // Validate id - if (!mongodb.ObjectID.isValid(ctx.params.id)) { - ctx.throw(400, 'incorrect id'); - return; - } - - const fileId = new mongodb.ObjectID(ctx.params.id); + const key = ctx.params.key; // Fetch drive file - const file = await DriveFile.findOne({ _id: fileId }); + const file = await DriveFiles.createQueryBuilder('file') + .where('file.accessKey = :accessKey', { accessKey: key }) + .orWhere('file.thumbnailAccessKey = :thumbnailAccessKey', { thumbnailAccessKey: key }) + .orWhere('file.webpublicAccessKey = :webpublicAccessKey', { webpublicAccessKey: key }) + .getOne(); if (file == null) { ctx.status = 404; @@ -33,69 +29,30 @@ export default async function(ctx: Koa.BaseContext) { return; } - if (file.metadata.deletedAt) { - ctx.status = 410; - await send(ctx as any, '/tombstone.png', { root: assets }); - return; - } - - if (file.metadata.withoutChunks) { + if (!file.storedInternal) { ctx.status = 204; return; } - const sendRaw = async () => { - if (file.metadata && file.metadata.accessKey && file.metadata.accessKey != ctx.query['original']) { - ctx.status = 403; - return; - } + const isThumbnail = file.thumbnailAccessKey === key; + const isWebpublic = file.webpublicAccessKey === key; - const bucket = await getDriveFileBucket(); - const readable = bucket.openDownloadStream(fileId); - readable.on('error', commonReadableHandlerGenerator(ctx)); - ctx.set('Content-Type', file.contentType); - ctx.body = readable; - }; - - if ('thumbnail' in ctx.query) { - const thumb = await DriveFileThumbnail.findOne({ - 'metadata.originalId': fileId - }); - - if (thumb != null) { - ctx.set('Content-Type', 'image/jpeg'); - ctx.set('Content-Disposition', contentDisposition('inline', `${rename(file.filename, { suffix: '-thumb', extname: '.jpeg' })}`)); - const bucket = await getDriveFileThumbnailBucket(); - ctx.body = bucket.openDownloadStream(thumb._id); - } else { - if (file.contentType.startsWith('image/')) { - ctx.set('Content-Disposition', contentDisposition('inline', `${file.filename}`)); - await sendRaw(); - } else { - ctx.status = 404; - await send(ctx as any, '/dummy.png', { root: assets }); - } - } - } else if ('web' in ctx.query) { - const web = await DriveFileWebpublic.findOne({ - 'metadata.originalId': fileId - }); - - if (web != null) { - ctx.set('Content-Type', file.contentType); - ctx.set('Content-Disposition', contentDisposition('inline', `${rename(file.filename, { suffix: '-web' })}`)); - - const bucket = await getDriveFileWebpublicBucket(); - ctx.body = bucket.openDownloadStream(web._id); - } else { - ctx.set('Content-Disposition', contentDisposition('inline', `${file.filename}`)); - await sendRaw(); - } + if (isThumbnail) { + ctx.set('Content-Type', 'image/jpeg'); + ctx.set('Content-Disposition', contentDisposition('inline', `${rename(file.name, { suffix: '-thumb', extname: '.jpeg' })}`)); + ctx.body = InternalStorage.read(key); + } else if (isWebpublic) { + ctx.set('Content-Type', file.type); + ctx.set('Content-Disposition', contentDisposition('inline', `${rename(file.name, { suffix: '-web' })}`)); + ctx.body = InternalStorage.read(key); } else { if ('download' in ctx.query) { - ctx.set('Content-Disposition', contentDisposition('attachment', `${file.filename}`)); + ctx.set('Content-Disposition', contentDisposition('attachment', `${file.name}`)); } - await sendRaw(); + const readable = InternalStorage.read(file.accessKey); + readable.on('error', commonReadableHandlerGenerator(ctx)); + ctx.set('Content-Type', file.type); + ctx.body = readable; } } diff --git a/src/server/index.ts b/src/server/index.ts index 7c51923f9e..563117773e 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -19,12 +19,12 @@ import activityPub from './activitypub'; import nodeinfo from './nodeinfo'; import wellKnown from './well-known'; import config from '../config'; -import networkChart from '../services/chart/network'; import apiServer from './api'; import { sum } from '../prelude/array'; -import User from '../models/user'; import Logger from '../services/logger'; import { program } from '../argv'; +import { Users } from '../models'; +import { networkChart } from '../services/chart'; export const serverLogger = new Logger('server', 'gray', false); @@ -73,17 +73,17 @@ router.use(nodeinfo.routes()); router.use(wellKnown.routes()); router.get('/verify-email/:code', async ctx => { - const user = await User.findOne({ emailVerifyCode: ctx.params.code }); + const user = await Users.findOne({ + emailVerifyCode: ctx.params.code + }); if (user != null) { ctx.body = 'Verify succeeded!'; ctx.status = 200; - User.update({ _id: user._id }, { - $set: { - emailVerified: true, - emailVerifyCode: null - } + Users.update(user.id, { + emailVerified: true, + emailVerifyCode: null }); } else { ctx.status = 404; diff --git a/src/server/nodeinfo.ts b/src/server/nodeinfo.ts index a783eea90b..686412383e 100644 --- a/src/server/nodeinfo.ts +++ b/src/server/nodeinfo.ts @@ -20,7 +20,24 @@ export const links = [/* (awaiting release) { const nodeinfo2 = async () => { const [ - { name, description, maintainer, langs, announcements, disableRegistration, disableLocalTimeline, disableGlobalTimeline, enableRecaptcha, maxNoteTextLength, enableTwitterIntegration, enableGithubIntegration, enableDiscordIntegration, enableEmail, enableServiceWorker }, + { + name, + description, + maintainerName, + maintainerEmail, + langs, + announcements, + disableRegistration, + disableLocalTimeline, + disableGlobalTimeline, + enableRecaptcha, + maxNoteTextLength, + enableTwitterIntegration, + enableGithubIntegration, + enableDiscordIntegration, + enableEmail, + enableServiceWorker + }, // total, // activeHalfyear, // activeMonth, @@ -52,7 +69,26 @@ const nodeinfo2 = async () => { // localPosts, // localComments }, - metadata: { name, description, maintainer, langs, announcements, disableRegistration, disableLocalTimeline, disableGlobalTimeline, enableRecaptcha, maxNoteTextLength, enableTwitterIntegration, enableGithubIntegration, enableDiscordIntegration, enableEmail, enableServiceWorker } + metadata: { + name, + description, + maintainer: { + name: maintainerName, + email: maintainerEmail + }, + langs, + announcements, + disableRegistration, + disableLocalTimeline, + disableGlobalTimeline, + enableRecaptcha, + maxNoteTextLength, + enableTwitterIntegration, + enableGithubIntegration, + enableDiscordIntegration, + enableEmail, + enableServiceWorker + } }; }; diff --git a/src/server/web/feed.ts b/src/server/web/feed.ts index 09ac10c576..4b4ea87973 100644 --- a/src/server/web/feed.ts +++ b/src/server/web/feed.ts @@ -1,25 +1,23 @@ import { Feed } from 'feed'; import config from '../../config'; -import Note from '../../models/note'; -import { IUser } from '../../models/user'; -import { getOriginalUrl } from '../../misc/get-drive-file-url'; +import { User } from '../../models/entities/user'; +import { Notes, DriveFiles } from '../../models'; +import { In } from 'typeorm'; -export default async function(user: IUser) { +export default async function(user: User) { const author: Author = { link: `${config.url}/@${user.username}`, name: user.name || user.username }; - const notes = await Note.find({ - userId: user._id, - renoteId: null, - $or: [ - { visibility: 'public' }, - { visibility: 'home' } - ] - }, { - sort: { createdAt: -1 }, - limit: 20 + const notes = await Notes.find({ + where: { + userId: user.id, + renoteId: null, + visibility: In(['public', 'home']) + }, + order: { createdAt: -1 }, + take: 20 }); const feed = new Feed({ @@ -38,15 +36,18 @@ export default async function(user: IUser) { } as FeedOptions); for (const note of notes) { - const file = note._files && note._files.find(file => file.contentType.startsWith('image/')); + const files = note.fileIds.length > 0 ? await DriveFiles.find({ + id: In(note.fileIds) + }) : []; + const file = files.find(file => file.type.startsWith('image/')); feed.addItem({ title: `New note by ${author.name}`, - link: `${config.url}/notes/${note._id}`, + link: `${config.url}/notes/${note.id}`, date: note.createdAt, description: note.cw, content: note.text, - image: file && getOriginalUrl(file) + image: file ? DriveFiles.getPublicUrl(file) : null }); } diff --git a/src/server/web/index.ts b/src/server/web/index.ts index d8525ba114..de0d65cf33 100644 --- a/src/server/web/index.ts +++ b/src/server/web/index.ts @@ -9,19 +9,16 @@ import * as Router from 'koa-router'; import * as send from 'koa-send'; import * as favicon from 'koa-favicon'; import * as views from 'koa-views'; -import { ObjectID } from 'mongodb'; import docs from './docs'; import packFeed from './feed'; -import User from '../../models/user'; -import parseAcct from '../../misc/acct/parse'; -import config from '../../config'; -import Note, { pack as packNote } from '../../models/note'; -import getNoteSummary from '../../misc/get-note-summary'; import fetchMeta from '../../misc/fetch-meta'; -import Emoji from '../../models/emoji'; import * as pkg from '../../../package.json'; import { genOpenapiSpec } from '../api/openapi/gen-spec'; +import config from '../../config'; +import { Users, Notes, Emojis } from '../../models'; +import parseAcct from '../../misc/acct/parse'; +import getNoteSummary from '../../misc/get-note-summary'; const client = `${__dirname}/../../client/`; @@ -100,7 +97,7 @@ router.get('/api.json', async ctx => { const getFeed = async (acct: string) => { const { username, host } = parseAcct(acct); - const user = await User.findOne({ + const user = await Users.findOne({ usernameLower: username.toLowerCase(), host }); @@ -148,7 +145,7 @@ router.get('/@:user.json', async ctx => { // User router.get('/@:user', async (ctx, next) => { const { username, host } = parseAcct(ctx.params.user); - const user = await User.findOne({ + const user = await Users.findOne({ usernameLower: username.toLowerCase(), host }); @@ -157,7 +154,7 @@ router.get('/@:user', async (ctx, next) => { const meta = await fetchMeta(); await ctx.render('user', { user, - instanceName: meta.name + instanceName: meta.name || 'Misskey' }); ctx.set('Cache-Control', 'public, max-age=180'); } else { @@ -167,19 +164,12 @@ router.get('/@:user', async (ctx, next) => { }); router.get('/users/:user', async ctx => { - if (!ObjectID.isValid(ctx.params.user)) { - ctx.status = 404; - return; - } - - const userId = new ObjectID(ctx.params.user); - - const user = await User.findOne({ - _id: userId, + const user = await Users.findOne({ + id: ctx.params.user, host: null }); - if (user === null) { + if (user == null) { ctx.status = 404; return; } @@ -189,26 +179,24 @@ router.get('/users/:user', async ctx => { // Note router.get('/notes/:note', async ctx => { - if (ObjectID.isValid(ctx.params.note)) { - const note = await Note.findOne({ _id: ctx.params.note }); + const note = await Notes.findOne(ctx.params.note); - if (note) { - const _note = await packNote(note); - const meta = await fetchMeta(); - await ctx.render('note', { - note: _note, - summary: getNoteSummary(_note), - instanceName: meta.name - }); - - if (['public', 'home'].includes(note.visibility)) { - ctx.set('Cache-Control', 'public, max-age=180'); - } else { - ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); - } + if (note) { + const _note = await Notes.pack(note); + const meta = await fetchMeta(); + await ctx.render('note', { + note: _note, + summary: getNoteSummary(_note), + instanceName: meta.name || 'Misskey' + }); - return; + if (['public', 'home'].includes(note.visibility)) { + ctx.set('Cache-Control', 'public, max-age=180'); + } else { + ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); } + + return; } ctx.status = 404; @@ -217,10 +205,8 @@ router.get('/notes/:note', async ctx => { router.get('/info', async ctx => { const meta = await fetchMeta(); - const emojis = await Emoji.find({ host: null }, { - fields: { - _id: false - } + const emojis = await Emojis.find({ + where: { host: null } }); await ctx.render('info', { version: pkg.version, @@ -232,7 +218,9 @@ router.get('/info', async ctx => { cores: os.cpus().length }, emojis: emojis, - meta: meta + meta: meta, + originalUsersCount: await Users.count({ host: null }), + originalNotesCount: await Notes.count({ userHost: null }) }); }); @@ -247,7 +235,7 @@ router.get('*', async ctx => { const meta = await fetchMeta(); await ctx.render('base', { img: meta.bannerUrl, - title: meta.name, + title: meta.name || 'Misskey', desc: meta.description, icon: meta.iconUrl }); diff --git a/src/server/web/manifest.ts b/src/server/web/manifest.ts index 35d3d1b666..4acfb22de5 100644 --- a/src/server/web/manifest.ts +++ b/src/server/web/manifest.ts @@ -1,10 +1,9 @@ import * as Koa from 'koa'; import * as manifest from '../../client/assets/manifest.json'; -import * as deepcopy from 'deepcopy'; import fetchMeta from '../../misc/fetch-meta'; module.exports = async (ctx: Koa.BaseContext) => { - const json = deepcopy(manifest); + const json = JSON.parse(JSON.stringify(manifest)); const instance = await fetchMeta(); diff --git a/src/server/web/views/info.pug b/src/server/web/views/info.pug index 1c4b272a62..c8b0bd939a 100644 --- a/src/server/web/views/info.pug +++ b/src/server/web/views/info.pug @@ -70,15 +70,15 @@ html table tr th Instance - td= meta.name + td= meta.name || 'Misskey' tr th Description td= meta.description tr th Maintainer td - = meta.maintainer.name - | <#{meta.maintainer.email}> + = meta.maintainerName + | <#{meta.maintainerEmail}> tr th System td= os @@ -93,10 +93,10 @@ html td= cpu.model tr th Original users - td= meta.stats.originalUsersCount + td= originalUsersCount tr th Original notes - td= meta.stats.originalNotesCount + td= originalNotesCount tr th Registration td= !meta.disableRegistration ? 'yes' : 'no' diff --git a/src/server/well-known.ts b/src/server/well-known.ts index 18c080acc7..7c5684d2ce 100644 --- a/src/server/well-known.ts +++ b/src/server/well-known.ts @@ -1,12 +1,12 @@ -import * as mongo from 'mongodb'; import * as Router from 'koa-router'; import config from '../config'; import parseAcct from '../misc/acct/parse'; -import User from '../models/user'; import Acct from '../misc/acct/type'; import { links } from './nodeinfo'; import { escapeAttribute, escapeValue } from '../prelude/xml'; +import { Users } from '../models'; +import { User } from '../models/entities/user'; // Init router const router = new Router(); @@ -47,19 +47,19 @@ router.get('/.well-known/nodeinfo', async ctx => { }); router.get(webFingerPath, async ctx => { + const fromId = (id: User['id']): Record<string, any> => ({ + id, + host: null + }); + const generateQuery = (resource: string) => resource.startsWith(`${config.url.toLowerCase()}/users/`) ? - fromId(new mongo.ObjectID(resource.split('/').pop())) : + fromId(resource.split('/').pop()) : fromAcct(parseAcct( resource.startsWith(`${config.url.toLowerCase()}/@`) ? resource.split('/').pop() : resource.startsWith('acct:') ? resource.slice('acct:'.length) : resource)); - const fromId = (_id: mongo.ObjectID): Record<string, any> => ({ - _id, - host: null - }); - const fromAcct = (acct: Acct): Record<string, any> | number => !acct.host || acct.host === config.host.toLowerCase() ? { usernameLower: acct.username, @@ -78,9 +78,9 @@ router.get(webFingerPath, async ctx => { return; } - const user = await User.findOne(query); + const user = await Users.findOne(query); - if (user === null) { + if (user == null) { ctx.status = 404; return; } @@ -89,7 +89,7 @@ router.get(webFingerPath, async ctx => { const self = { rel: 'self', type: 'application/activity+json', - href: `${config.url}/users/${user._id}` + href: `${config.url}/users/${user.id}` }; const profilePage = { rel: 'http://webfinger.net/rel/profile-page', |