summaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2018-04-25 19:53:16 +0900
committersyuilo <syuilotan@yahoo.co.jp>2018-04-25 19:53:16 +0900
commitc2e053a208609d59188dce9e328c1ab9706aa35c (patch)
tree88e6b66523d246b0b4ab11ed6961e09f6fe0d23b /src/server
parentwip (diff)
downloadsharkey-c2e053a208609d59188dce9e328c1ab9706aa35c.tar.gz
sharkey-c2e053a208609d59188dce9e328c1ab9706aa35c.tar.bz2
sharkey-c2e053a208609d59188dce9e328c1ab9706aa35c.zip
wip
Diffstat (limited to 'src/server')
-rw-r--r--src/server/api/endpoints.ts29
-rw-r--r--src/server/api/endpoints/notes/user-list-timeline.ts179
-rw-r--r--src/server/api/endpoints/users/lists/show.ts23
-rw-r--r--src/server/api/endpoints/users/search_by_username.ts6
4 files changed, 232 insertions, 5 deletions
diff --git a/src/server/api/endpoints.ts b/src/server/api/endpoints.ts
index 3686918147..734b8273f1 100644
--- a/src/server/api/endpoints.ts
+++ b/src/server/api/endpoints.ts
@@ -415,6 +415,27 @@ const endpoints: Endpoint[] = [
},
{
+ name: 'users/lists/show',
+ withCredential: true,
+ kind: 'account-read'
+ },
+ {
+ name: 'users/lists/create',
+ withCredential: true,
+ kind: 'account-write'
+ },
+ {
+ name: 'users/lists/push',
+ withCredential: true,
+ kind: 'account-write'
+ },
+ {
+ name: 'users/lists/list',
+ withCredential: true,
+ kind: 'account-read'
+ },
+
+ {
name: 'following/create',
withCredential: true,
limit: {
@@ -504,6 +525,14 @@ const endpoints: Endpoint[] = [
}
},
{
+ name: 'notes/user-list-timeline',
+ withCredential: true,
+ limit: {
+ duration: ms('10minutes'),
+ max: 100
+ }
+ },
+ {
name: 'notes/mentions',
withCredential: true,
limit: {
diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts
new file mode 100644
index 0000000000..bb94fa0ab9
--- /dev/null
+++ b/src/server/api/endpoints/notes/user-list-timeline.ts
@@ -0,0 +1,179 @@
+/**
+ * Module dependencies
+ */
+import $ from 'cafy'; import ID from '../../../../cafy-id';
+import Note from '../../../../models/note';
+import Mute from '../../../../models/mute';
+import { pack } from '../../../../models/note';
+import UserList from '../../../../models/user-list';
+
+/**
+ * Get timeline of a user list
+ */
+module.exports = async (params, user, app) => {
+ // Get 'limit' parameter
+ const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$;
+ if (limitErr) throw 'invalid limit param';
+
+ // Get 'sinceId' parameter
+ const [sinceId, sinceIdErr] = $(params.sinceId).optional.type(ID).$;
+ if (sinceIdErr) throw 'invalid sinceId param';
+
+ // Get 'untilId' parameter
+ const [untilId, untilIdErr] = $(params.untilId).optional.type(ID).$;
+ if (untilIdErr) throw 'invalid untilId param';
+
+ // Get 'sinceDate' parameter
+ const [sinceDate, sinceDateErr] = $(params.sinceDate).optional.number().$;
+ if (sinceDateErr) throw 'invalid sinceDate param';
+
+ // Get 'untilDate' parameter
+ const [untilDate, untilDateErr] = $(params.untilDate).optional.number().$;
+ if (untilDateErr) throw 'invalid untilDate param';
+
+ // Check if only one of sinceId, untilId, sinceDate, untilDate specified
+ if ([sinceId, untilId, sinceDate, untilDate].filter(x => x != null).length > 1) {
+ throw 'only one of sinceId, untilId, sinceDate, untilDate can be specified';
+ }
+
+ // Get 'includeMyRenotes' parameter
+ const [includeMyRenotes = true, includeMyRenotesErr] = $(params.includeMyRenotes).optional.boolean().$;
+ if (includeMyRenotesErr) throw 'invalid includeMyRenotes param';
+
+ // Get 'includeRenotedMyNotes' parameter
+ const [includeRenotedMyNotes = true, includeRenotedMyNotesErr] = $(params.includeRenotedMyNotes).optional.boolean().$;
+ if (includeRenotedMyNotesErr) throw 'invalid includeRenotedMyNotes param';
+
+ // Get 'listId' parameter
+ const [listId, listIdErr] = $(params.listId).type(ID).$;
+ if (listIdErr) throw 'invalid listId param';
+
+ const [list, mutedUserIds] = await Promise.all([
+ // リストを取得
+ // Fetch the list
+ UserList.findOne({
+ _id: listId,
+ userId: user._id
+ }),
+
+ // ミュートしているユーザーを取得
+ Mute.find({
+ muterId: user._id
+ }).then(ms => ms.map(m => m.muteeId))
+ ]);
+
+ 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 query = {
+ $and: [{
+ // リストに入っている人のタイムラインへの投稿
+ $or: listQuery,
+
+ // mute
+ userId: {
+ $nin: mutedUserIds
+ },
+ '_reply.userId': {
+ $nin: mutedUserIds
+ },
+ '_renote.userId': {
+ $nin: mutedUserIds
+ },
+ }]
+ } as any;
+
+ // MongoDBではトップレベルで否定ができないため、De Morganの法則を利用してクエリします。
+ // つまり、「『自分の投稿かつRenote』ではない」を「『自分の投稿ではない』または『Renoteではない』」と表現します。
+ // for details: https://en.wikipedia.org/wiki/De_Morgan%27s_laws
+
+ if (includeMyRenotes === false) {
+ query.$and.push({
+ $or: [{
+ userId: { $ne: user._id }
+ }, {
+ renoteId: null
+ }, {
+ text: { $ne: null }
+ }, {
+ mediaIds: { $ne: [] }
+ }, {
+ poll: { $ne: null }
+ }]
+ });
+ }
+
+ if (includeRenotedMyNotes === false) {
+ query.$and.push({
+ $or: [{
+ '_renote.userId': { $ne: user._id }
+ }, {
+ renoteId: null
+ }, {
+ text: { $ne: null }
+ }, {
+ mediaIds: { $ne: [] }
+ }, {
+ poll: { $ne: null }
+ }]
+ });
+ }
+
+ if (sinceId) {
+ sort._id = 1;
+ query._id = {
+ $gt: sinceId
+ };
+ } else if (untilId) {
+ query._id = {
+ $lt: untilId
+ };
+ } else if (sinceDate) {
+ sort._id = 1;
+ query.createdAt = {
+ $gt: new Date(sinceDate)
+ };
+ } else if (untilDate) {
+ query.createdAt = {
+ $lt: new Date(untilDate)
+ };
+ }
+ //#endregion
+
+ // Issue query
+ const timeline = await Note
+ .find(query, {
+ limit: limit,
+ sort: sort
+ });
+
+ // Serialize
+ return await Promise.all(timeline.map(note => pack(note, user)));
+};
diff --git a/src/server/api/endpoints/users/lists/show.ts b/src/server/api/endpoints/users/lists/show.ts
new file mode 100644
index 0000000000..61e0f0463f
--- /dev/null
+++ b/src/server/api/endpoints/users/lists/show.ts
@@ -0,0 +1,23 @@
+import $ from 'cafy'; import ID from '../../../../../cafy-id';
+import UserList, { pack } from '../../../../../models/user-list';
+
+/**
+ * Show a user list
+ */
+module.exports = async (params, me) => new Promise(async (res, rej) => {
+ // Get 'listId' parameter
+ const [listId, listIdErr] = $(params.listId).type(ID).$;
+ if (listIdErr) return rej('invalid listId param');
+
+ // Fetch the list
+ const userList = await UserList.findOne({
+ _id: listId,
+ userId: me._id,
+ });
+
+ if (userList == null) {
+ return rej('list not found');
+ }
+
+ res(await pack(userList));
+});
diff --git a/src/server/api/endpoints/users/search_by_username.ts b/src/server/api/endpoints/users/search_by_username.ts
index 41a12d5332..91d9ad1f3a 100644
--- a/src/server/api/endpoints/users/search_by_username.ts
+++ b/src/server/api/endpoints/users/search_by_username.ts
@@ -1,15 +1,11 @@
/**
* Module dependencies
*/
-import $ from 'cafy'; import ID from '../../../../cafy-id';
+import $ from 'cafy';
import User, { pack } from '../../../../models/user';
/**
* Search a user by username
- *
- * @param {any} params
- * @param {any} me
- * @return {Promise<any>}
*/
module.exports = (params, me) => new Promise(async (res, rej) => {
// Get 'query' parameter