diff options
Diffstat (limited to 'src/server/api/endpoints')
52 files changed, 944 insertions, 554 deletions
diff --git a/src/server/api/endpoints/aggregation/users/followers.ts b/src/server/api/endpoints/aggregation/users/followers.ts deleted file mode 100644 index 94eb83febc..0000000000 --- a/src/server/api/endpoints/aggregation/users/followers.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; -import User from '../../../../../models/user'; -import FollowedLog from '../../../../../models/followed-log'; - -/** - * Aggregate followers of a user - */ -export default (params: any) => new Promise(async (res, rej) => { - // Get 'userId' parameter - const [userId, userIdErr] = $.type(ID).get(params.userId); - if (userIdErr) return rej('invalid userId param'); - - // Lookup user - const user = await User.findOne({ - _id: userId - }, { - fields: { - _id: true - } - }); - - if (user === null) { - return rej('user not found'); - } - - const today = new Date(); - const graph = []; - - today.setMinutes(0); - today.setSeconds(0); - today.setMilliseconds(0); - - let cursorDate = new Date(today.getTime()); - let cursorTime = cursorDate.setDate(new Date(today.getTime()).getDate() + 1); - - for (let i = 0; i < 30; i++) { - graph.push(FollowedLog.findOne({ - createdAt: { $lt: new Date(cursorTime / 1000) }, - userId: user._id - }, { - sort: { createdAt: -1 }, - }).then(log => { - cursorDate = new Date(today.getTime()); - cursorTime = cursorDate.setDate(today.getDate() - i); - - return { - date: { - year: cursorDate.getFullYear(), - month: cursorDate.getMonth() + 1, // In JavaScript, month is zero-based. - day: cursorDate.getDate() - }, - count: log ? log.count : 0 - }; - })); - } - - res(await Promise.all(graph)); -}); diff --git a/src/server/api/endpoints/aggregation/users/following.ts b/src/server/api/endpoints/aggregation/users/following.ts deleted file mode 100644 index d2e4d256fe..0000000000 --- a/src/server/api/endpoints/aggregation/users/following.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; -import User from '../../../../../models/user'; -import FollowingLog from '../../../../../models/following-log'; - -/** - * Aggregate following of a user - */ -export default (params: any) => new Promise(async (res, rej) => { - // Get 'userId' parameter - const [userId, userIdErr] = $.type(ID).get(params.userId); - if (userIdErr) return rej('invalid userId param'); - - // Lookup user - const user = await User.findOne({ - _id: userId - }, { - fields: { - _id: true - } - }); - - if (user === null) { - return rej('user not found'); - } - - const today = new Date(); - const graph = []; - - today.setMinutes(0); - today.setSeconds(0); - today.setMilliseconds(0); - - let cursorDate = new Date(today.getTime()); - let cursorTime = cursorDate.setDate(new Date(today.getTime()).getDate() + 1); - - for (let i = 0; i < 30; i++) { - graph.push(FollowingLog.findOne({ - createdAt: { $lt: new Date(cursorTime / 1000) }, - userId: user._id - }, { - sort: { createdAt: -1 }, - }).then(log => { - cursorDate = new Date(today.getTime()); - cursorTime = cursorDate.setDate(today.getDate() - i); - - return { - date: { - year: cursorDate.getFullYear(), - month: cursorDate.getMonth() + 1, // In JavaScript, month is zero-based. - day: cursorDate.getDate() - }, - count: log ? log.count : 0 - }; - })); - } - - res(await Promise.all(graph)); -}); diff --git a/src/server/api/endpoints/ap/show.ts b/src/server/api/endpoints/ap/show.ts index 1f390d01aa..6cbd4ef87e 100644 --- a/src/server/api/endpoints/ap/show.ts +++ b/src/server/api/endpoints/ap/show.ts @@ -24,15 +24,15 @@ export const meta = { }, }; -export default (params: any) => new Promise(async (res, rej) => { +export default async (params: any) => { const [ps, psErr] = getParams(meta, params); - if (psErr) return rej(psErr); + if (psErr) throw psErr; const object = await fetchAny(ps.uri); - if (object !== null) return res(object); + if (object !== null) return object; - return rej('object not found'); -}); + throw new Error('object not found'); +}; /*** * URIからUserかNoteを解決する diff --git a/src/server/api/endpoints/chart.ts b/src/server/api/endpoints/chart.ts deleted file mode 100644 index 3b1a3b56fc..0000000000 --- a/src/server/api/endpoints/chart.ts +++ /dev/null @@ -1,277 +0,0 @@ -import $ from 'cafy'; -import Stats, { IStats } from '../../../models/stats'; -import getParams from '../get-params'; - -type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; - -function migrateStats(stats: IStats[]) { - stats.forEach(stat => { - if (stat.network == null) { - stat.network = { - requests: 0, - totalTime: 0, - incomingBytes: 0, - outgoingBytes: 0 - }; - } - - const isOldData = - stat.users.local.inc == null || - stat.users.local.dec == null || - stat.users.remote.inc == null || - stat.users.remote.dec == null || - stat.notes.local.inc == null || - stat.notes.local.dec == null || - stat.notes.remote.inc == null || - stat.notes.remote.dec == null || - stat.drive.local.incCount == null || - stat.drive.local.decCount == null || - stat.drive.local.incSize == null || - stat.drive.local.decSize == null || - stat.drive.remote.incCount == null || - stat.drive.remote.decCount == null || - stat.drive.remote.incSize == null || - stat.drive.remote.decSize == null; - - if (!isOldData) return; - - stat.users.local.inc = (stat as any).users.local.diff; - stat.users.local.dec = 0; - stat.users.remote.inc = (stat as any).users.remote.diff; - stat.users.remote.dec = 0; - stat.notes.local.inc = (stat as any).notes.local.diff; - stat.notes.local.dec = 0; - stat.notes.remote.inc = (stat as any).notes.remote.diff; - stat.notes.remote.dec = 0; - stat.drive.local.incCount = (stat as any).drive.local.diffCount; - stat.drive.local.decCount = 0; - stat.drive.local.incSize = (stat as any).drive.local.diffSize; - stat.drive.local.decSize = 0; - stat.drive.remote.incCount = (stat as any).drive.remote.diffCount; - stat.drive.remote.decCount = 0; - stat.drive.remote.incSize = (stat as any).drive.remote.diffSize; - stat.drive.remote.decSize = 0; - }); -} - -export const meta = { - desc: { - 'ja-JP': 'インスタンスの統計を取得します。' - }, - - params: { - limit: $.num.optional.range(1, 100).note({ - default: 30, - desc: { - 'ja-JP': '最大数' - } - }), - } -}; - -export default (params: any) => new Promise(async (res, rej) => { - const [ps, psErr] = getParams(meta, params); - if (psErr) throw psErr; - - const daysRange = ps.limit; - const hoursRange = ps.limit; - - const now = new Date(); - const y = now.getFullYear(); - const m = now.getMonth(); - const d = now.getDate(); - const h = now.getHours(); - - const [statsPerDay, statsPerHour] = await Promise.all([ - Stats.find({ - span: 'day', - date: { - $gt: new Date(y, m, d - daysRange) - } - }, { - sort: { - date: -1 - }, - fields: { - _id: 0 - } - }), - Stats.find({ - span: 'hour', - date: { - $gt: new Date(y, m, d, h - hoursRange) - } - }, { - sort: { - date: -1 - }, - fields: { - _id: 0 - } - }), - ]); - - // 後方互換性のため - migrateStats(statsPerDay); - migrateStats(statsPerHour); - - const format = (src: IStats[], span: 'day' | 'hour') => { - const chart: Array<Omit<Omit<IStats, '_id'>, 'span'>> = []; - - const range = - span == 'day' ? daysRange : - span == 'hour' ? hoursRange : - null; - - for (let i = (range - 1); i >= 0; i--) { - const current = - span == 'day' ? new Date(y, m, d - i) : - span == 'hour' ? new Date(y, m, d, h - i) : - null; - - const stat = src.find(s => s.date.getTime() == current.getTime()); - - if (stat) { - chart.unshift(stat); - } else { // 隙間埋め - const mostRecent = src.find(s => s.date.getTime() < current.getTime()); - if (mostRecent) { - chart.unshift({ - date: current, - users: { - local: { - total: mostRecent.users.local.total, - inc: 0, - dec: 0 - }, - remote: { - total: mostRecent.users.remote.total, - inc: 0, - dec: 0 - } - }, - notes: { - local: { - total: mostRecent.notes.local.total, - inc: 0, - dec: 0, - diffs: { - normal: 0, - reply: 0, - renote: 0 - } - }, - remote: { - total: mostRecent.notes.remote.total, - inc: 0, - dec: 0, - diffs: { - normal: 0, - reply: 0, - renote: 0 - } - } - }, - drive: { - local: { - totalCount: mostRecent.drive.local.totalCount, - totalSize: mostRecent.drive.local.totalSize, - incCount: 0, - incSize: 0, - decCount: 0, - decSize: 0 - }, - remote: { - totalCount: mostRecent.drive.remote.totalCount, - totalSize: mostRecent.drive.remote.totalSize, - incCount: 0, - incSize: 0, - decCount: 0, - decSize: 0 - } - }, - network: { - requests: 0, - totalTime: 0, - incomingBytes: 0, - outgoingBytes: 0 - } - }); - } else { - chart.unshift({ - date: current, - users: { - local: { - total: 0, - inc: 0, - dec: 0 - }, - remote: { - total: 0, - inc: 0, - dec: 0 - } - }, - notes: { - local: { - total: 0, - inc: 0, - dec: 0, - diffs: { - normal: 0, - reply: 0, - renote: 0 - } - }, - remote: { - total: 0, - inc: 0, - dec: 0, - diffs: { - normal: 0, - reply: 0, - renote: 0 - } - } - }, - drive: { - local: { - totalCount: 0, - totalSize: 0, - incCount: 0, - incSize: 0, - decCount: 0, - decSize: 0 - }, - remote: { - totalCount: 0, - totalSize: 0, - incCount: 0, - incSize: 0, - decCount: 0, - decSize: 0 - } - }, - network: { - requests: 0, - totalTime: 0, - incomingBytes: 0, - outgoingBytes: 0 - } - }); - } - } - } - - chart.forEach(x => { - delete (x as any).span; - }); - - return chart; - }; - - res({ - perDay: format(statsPerDay, 'day'), - perHour: format(statsPerHour, 'hour') - }); -}); diff --git a/src/server/api/endpoints/charts/drive.ts b/src/server/api/endpoints/charts/drive.ts new file mode 100644 index 0000000000..dc18331847 --- /dev/null +++ b/src/server/api/endpoints/charts/drive.ts @@ -0,0 +1,33 @@ +import $ from 'cafy'; +import getParams from '../../get-params'; +import driveChart from '../../../../chart/drive'; + +export const meta = { + desc: { + 'ja-JP': 'ドライブのチャートを取得します。' + }, + + params: { + span: $.str.or(['day', 'hour']).note({ + desc: { + 'ja-JP': '集計のスパン (day または hour)' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 30, + desc: { + 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' + } + }), + } +}; + +export default (params: any) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + + const stats = await driveChart.getChart(ps.span as any, ps.limit); + + res(stats); +}); diff --git a/src/server/api/endpoints/charts/federation.ts b/src/server/api/endpoints/charts/federation.ts new file mode 100644 index 0000000000..5b24783c69 --- /dev/null +++ b/src/server/api/endpoints/charts/federation.ts @@ -0,0 +1,33 @@ +import $ from 'cafy'; +import getParams from '../../get-params'; +import federationChart from '../../../../chart/federation'; + +export const meta = { + desc: { + 'ja-JP': 'フェデレーションのチャートを取得します。' + }, + + params: { + span: $.str.or(['day', 'hour']).note({ + desc: { + 'ja-JP': '集計のスパン (day または hour)' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 30, + desc: { + 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' + } + }), + } +}; + +export default (params: any) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + + const stats = await federationChart.getChart(ps.span as any, ps.limit); + + res(stats); +}); diff --git a/src/server/api/endpoints/charts/hashtag.ts b/src/server/api/endpoints/charts/hashtag.ts new file mode 100644 index 0000000000..bcd48dc485 --- /dev/null +++ b/src/server/api/endpoints/charts/hashtag.ts @@ -0,0 +1,39 @@ +import $ from 'cafy'; +import getParams from '../../get-params'; +import hashtagChart from '../../../../chart/hashtag'; + +export const meta = { + desc: { + 'ja-JP': 'ハッシュタグごとのチャートを取得します。' + }, + + params: { + span: $.str.or(['day', 'hour']).note({ + desc: { + 'ja-JP': '集計のスパン (day または hour)' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 30, + desc: { + 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' + } + }), + + tag: $.str.note({ + desc: { + 'ja-JP': '対象のハッシュタグ' + } + }), + } +}; + +export default (params: any) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + + const stats = await hashtagChart.getChart(ps.span as any, ps.limit, ps.tag); + + res(stats); +}); diff --git a/src/server/api/endpoints/charts/network.ts b/src/server/api/endpoints/charts/network.ts new file mode 100644 index 0000000000..d5b0791994 --- /dev/null +++ b/src/server/api/endpoints/charts/network.ts @@ -0,0 +1,33 @@ +import $ from 'cafy'; +import getParams from '../../get-params'; +import networkChart from '../../../../chart/network'; + +export const meta = { + desc: { + 'ja-JP': 'ネットワークのチャートを取得します。' + }, + + params: { + span: $.str.or(['day', 'hour']).note({ + desc: { + 'ja-JP': '集計のスパン (day または hour)' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 30, + desc: { + 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' + } + }), + } +}; + +export default (params: any) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + + const stats = await networkChart.getChart(ps.span as any, ps.limit); + + res(stats); +}); diff --git a/src/server/api/endpoints/charts/notes.ts b/src/server/api/endpoints/charts/notes.ts new file mode 100644 index 0000000000..573b012469 --- /dev/null +++ b/src/server/api/endpoints/charts/notes.ts @@ -0,0 +1,33 @@ +import $ from 'cafy'; +import getParams from '../../get-params'; +import notesChart from '../../../../chart/notes'; + +export const meta = { + desc: { + 'ja-JP': '投稿のチャートを取得します。' + }, + + params: { + span: $.str.or(['day', 'hour']).note({ + desc: { + 'ja-JP': '集計のスパン (day または hour)' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 30, + desc: { + 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' + } + }), + } +}; + +export default (params: any) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + + const stats = await notesChart.getChart(ps.span as any, ps.limit); + + res(stats); +}); diff --git a/src/server/api/endpoints/charts/user/drive.ts b/src/server/api/endpoints/charts/user/drive.ts new file mode 100644 index 0000000000..2626c36c9d --- /dev/null +++ b/src/server/api/endpoints/charts/user/drive.ts @@ -0,0 +1,41 @@ +import $ from 'cafy'; +import getParams from '../../../get-params'; +import perUserDriveChart from '../../../../../chart/per-user-drive'; +import ID from '../../../../../misc/cafy-id'; + +export const meta = { + desc: { + 'ja-JP': 'ユーザーごとのドライブのチャートを取得します。' + }, + + params: { + span: $.str.or(['day', 'hour']).note({ + desc: { + 'ja-JP': '集計のスパン (day または hour)' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 30, + desc: { + 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' + } + }), + + userId: $.type(ID).note({ + desc: { + 'ja-JP': '対象のユーザーのID', + 'en-US': 'Target user ID' + } + }) + } +}; + +export default (params: any) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + + const stats = await perUserDriveChart.getChart(ps.span as any, ps.limit, ps.userId); + + res(stats); +}); diff --git a/src/server/api/endpoints/charts/user/following.ts b/src/server/api/endpoints/charts/user/following.ts new file mode 100644 index 0000000000..57c15cdcfe --- /dev/null +++ b/src/server/api/endpoints/charts/user/following.ts @@ -0,0 +1,41 @@ +import $ from 'cafy'; +import getParams from '../../../get-params'; +import perUserFollowingChart from '../../../../../chart/per-user-following'; +import ID from '../../../../../misc/cafy-id'; + +export const meta = { + desc: { + 'ja-JP': 'ユーザーごとのフォロー/フォロワーのチャートを取得します。' + }, + + params: { + span: $.str.or(['day', 'hour']).note({ + desc: { + 'ja-JP': '集計のスパン (day または hour)' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 30, + desc: { + 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' + } + }), + + userId: $.type(ID).note({ + desc: { + 'ja-JP': '対象のユーザーのID', + 'en-US': 'Target user ID' + } + }) + } +}; + +export default (params: any) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + + const stats = await perUserFollowingChart.getChart(ps.span as any, ps.limit, ps.userId); + + res(stats); +}); diff --git a/src/server/api/endpoints/charts/user/notes.ts b/src/server/api/endpoints/charts/user/notes.ts new file mode 100644 index 0000000000..66051fa7c6 --- /dev/null +++ b/src/server/api/endpoints/charts/user/notes.ts @@ -0,0 +1,41 @@ +import $ from 'cafy'; +import getParams from '../../../get-params'; +import perUserNotesChart from '../../../../../chart/per-user-notes'; +import ID from '../../../../../misc/cafy-id'; + +export const meta = { + desc: { + 'ja-JP': 'ユーザーごとの投稿のチャートを取得します。' + }, + + params: { + span: $.str.or(['day', 'hour']).note({ + desc: { + 'ja-JP': '集計のスパン (day または hour)' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 30, + desc: { + 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' + } + }), + + userId: $.type(ID).note({ + desc: { + 'ja-JP': '対象のユーザーのID', + 'en-US': 'Target user ID' + } + }) + } +}; + +export default (params: any) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + + const stats = await perUserNotesChart.getChart(ps.span as any, ps.limit, ps.userId); + + res(stats); +}); diff --git a/src/server/api/endpoints/charts/user/reactions.ts b/src/server/api/endpoints/charts/user/reactions.ts new file mode 100644 index 0000000000..60cdaa70bb --- /dev/null +++ b/src/server/api/endpoints/charts/user/reactions.ts @@ -0,0 +1,41 @@ +import $ from 'cafy'; +import getParams from '../../../get-params'; +import perUserReactionsChart from '../../../../../chart/per-user-reactions'; +import ID from '../../../../../misc/cafy-id'; + +export const meta = { + desc: { + 'ja-JP': 'ユーザーごとの被リアクション数のチャートを取得します。' + }, + + params: { + span: $.str.or(['day', 'hour']).note({ + desc: { + 'ja-JP': '集計のスパン (day または hour)' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 30, + desc: { + 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' + } + }), + + userId: $.type(ID).note({ + desc: { + 'ja-JP': '対象のユーザーのID', + 'en-US': 'Target user ID' + } + }) + } +}; + +export default (params: any) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + + const stats = await perUserReactionsChart.getChart(ps.span as any, ps.limit, ps.userId); + + res(stats); +}); diff --git a/src/server/api/endpoints/charts/users.ts b/src/server/api/endpoints/charts/users.ts new file mode 100644 index 0000000000..595bb63f0b --- /dev/null +++ b/src/server/api/endpoints/charts/users.ts @@ -0,0 +1,33 @@ +import $ from 'cafy'; +import getParams from '../../get-params'; +import usersChart from '../../../../chart/users'; + +export const meta = { + desc: { + 'ja-JP': 'ユーザーのチャートを取得します。' + }, + + params: { + span: $.str.or(['day', 'hour']).note({ + desc: { + 'ja-JP': '集計のスパン (day または hour)' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 30, + desc: { + 'ja-JP': '最大数。例えば 30 を指定したとすると、スパンが"day"の場合は30日分のデータが、スパンが"hour"の場合は30時間分のデータが返ります。' + } + }), + } +}; + +export default (params: any) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + + const stats = await usersChart.getChart(ps.span as any, ps.limit); + + res(stats); +}); diff --git a/src/server/api/endpoints/drive/files/attached_notes.ts b/src/server/api/endpoints/drive/files/attached_notes.ts new file mode 100644 index 0000000000..1187169c64 --- /dev/null +++ b/src/server/api/endpoints/drive/files/attached_notes.ts @@ -0,0 +1,48 @@ +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import DriveFile from '../../../../../models/drive-file'; +import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; +import { packMany } from '../../../../../models/note'; + +export const meta = { + stability: 'stable', + + desc: { + 'ja-JP': '指定したドライブのファイルが添付されている投稿一覧を取得します。', + 'en-US': 'Get the notes that specified file of drive attached.' + }, + + requireCredential: true, + + kind: 'drive-read', + + params: { + fileId: $.type(ID).note({ + desc: { + 'ja-JP': '対象のファイルID', + 'en-US': 'Target file ID' + } + }) + } +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + + // Fetch file + const file = await DriveFile + .findOne({ + _id: ps.fileId, + 'metadata.userId': user._id, + 'metadata.deletedAt': { $exists: false } + }); + + if (file === null) { + return rej('file-not-found'); + } + + res(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 73d75b7caf..a024701655 100644 --- a/src/server/api/endpoints/drive/files/check_existence.ts +++ b/src/server/api/endpoints/drive/files/check_existence.ts @@ -27,7 +27,8 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = const file = await DriveFile.findOne({ md5: md5, - 'metadata.userId': user._id + 'metadata.userId': user._id, + 'metadata.deletedAt': { $exists: false } }); if (file === null) { diff --git a/src/server/api/endpoints/drive/files/create.ts b/src/server/api/endpoints/drive/files/create.ts index 4b5ffa90e0..29e65a7ad3 100644 --- a/src/server/api/endpoints/drive/files/create.ts +++ b/src/server/api/endpoints/drive/files/create.ts @@ -31,19 +31,23 @@ export const meta = { } }), - isSensitive: $.bool.optional.nullable.note({ - default: null, + isSensitive: $.bool.optional.note({ + default: false, desc: { 'ja-JP': 'このメディアが「閲覧注意」(NSFW)かどうか', 'en-US': 'Whether this media is NSFW' } + }), + + force: $.bool.optional.note({ + default: false, + desc: { + 'ja-JP': 'true にすると、同じハッシュを持つファイルが既にアップロードされていても強制的にファイルを作成します。', + } }) } }; -/** - * Create a file - */ export default async (file: any, params: any, user: ILocalUser): Promise<any> => { if (file == null) { throw 'file is required'; @@ -76,7 +80,7 @@ export default async (file: any, params: any, user: ILocalUser): Promise<any> => try { // Create file - const driveFile = await create(user, file.path, name, null, ps.folderId, false, false, null, null, ps.isSensitive); + const driveFile = await create(user, file.path, name, null, ps.folderId, ps.force, false, null, null, ps.isSensitive); cleanup(); diff --git a/src/server/api/endpoints/drive/files/delete.ts b/src/server/api/endpoints/drive/files/delete.ts index fb7340df38..fc6849e57e 100644 --- a/src/server/api/endpoints/drive/files/delete.ts +++ b/src/server/api/endpoints/drive/files/delete.ts @@ -3,8 +3,11 @@ import DriveFile from '../../../../../models/drive-file'; import del from '../../../../../services/drive/delete-file'; import { publishDriveStream } from '../../../../../stream'; import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; export const meta = { + stability: 'stable', + desc: { 'ja-JP': 'ドライブのファイルを削除します。', 'en-US': 'Delete a file of drive.' @@ -12,30 +15,38 @@ export const meta = { requireCredential: true, - kind: 'drive-write' + kind: 'drive-write', + + params: { + fileId: $.type(ID).note({ + desc: { + 'ja-JP': '対象のファイルID', + 'en-US': 'Target file ID' + } + }) + } }; -export default async (params: any, user: ILocalUser) => { - // Get 'fileId' parameter - const [fileId, fileIdErr] = $.type(ID).get(params.fileId); - if (fileIdErr) throw 'invalid fileId param'; +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Fetch file const file = await DriveFile .findOne({ - _id: fileId, + _id: ps.fileId, 'metadata.userId': user._id }); if (file === null) { - throw 'file-not-found'; + return rej('file-not-found'); } // Delete await del(file); - // Publish file_deleted event - publishDriveStream(user._id, 'file_deleted', file._id); + // Publish fileDeleted event + publishDriveStream(user._id, 'fileDeleted', file._id); - return; -}; + res(); +}); diff --git a/src/server/api/endpoints/drive/files/show.ts b/src/server/api/endpoints/drive/files/show.ts index 718fb8c2d7..49d6027add 100644 --- a/src/server/api/endpoints/drive/files/show.ts +++ b/src/server/api/endpoints/drive/files/show.ts @@ -1,8 +1,11 @@ import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import DriveFile, { pack } from '../../../../../models/drive-file'; import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; export const meta = { + stability: 'stable', + desc: { 'ja-JP': '指定したドライブのファイルの情報を取得します。', 'en-US': 'Get specified file of drive.' @@ -10,23 +13,32 @@ export const meta = { requireCredential: true, - kind: 'drive-read' + kind: 'drive-read', + + params: { + fileId: $.type(ID).note({ + desc: { + 'ja-JP': '対象のファイルID', + 'en-US': 'Target file ID' + } + }) + } }; -export default async (params: any, user: ILocalUser) => { - // Get 'fileId' parameter - const [fileId, fileIdErr] = $.type(ID).get(params.fileId); - if (fileIdErr) throw 'invalid fileId param'; +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Fetch file const file = await DriveFile .findOne({ - _id: fileId, - 'metadata.userId': user._id + _id: ps.fileId, + 'metadata.userId': user._id, + 'metadata.deletedAt': { $exists: false } }); if (file === null) { - throw 'file-not-found'; + return rej('file-not-found'); } // Serialize @@ -34,5 +46,5 @@ export default async (params: any, user: ILocalUser) => { detail: true }); - return _file; -}; + res(_file); +}); diff --git a/src/server/api/endpoints/drive/files/update.ts b/src/server/api/endpoints/drive/files/update.ts index 3c7932c341..915cf4ceb2 100644 --- a/src/server/api/endpoints/drive/files/update.ts +++ b/src/server/api/endpoints/drive/files/update.ts @@ -100,8 +100,10 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = }).then(notes => { notes.forEach(note => { note._files[note._files.findIndex(f => f._id.equals(file._id))] = file; - Note.findOneAndUpdate({ _id: note._id }, { - _files: note._files + Note.update({ _id: note._id }, { + $set: { + _files: note._files + } }); }); }); @@ -112,6 +114,6 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = // Response res(fileObj); - // Publish file_updated event - publishDriveStream(user._id, 'file_updated', fileObj); + // Publish fileUpdated event + publishDriveStream(user._id, 'fileUpdated', fileObj); }); diff --git a/src/server/api/endpoints/drive/folders/create.ts b/src/server/api/endpoints/drive/folders/create.ts index 5997dedf0f..cca25b0596 100644 --- a/src/server/api/endpoints/drive/folders/create.ts +++ b/src/server/api/endpoints/drive/folders/create.ts @@ -2,8 +2,11 @@ import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import DriveFolder, { isValidFolderName, pack } from '../../../../../models/drive-folder'; import { publishDriveStream } from '../../../../../stream'; import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; export const meta = { + stability: 'stable', + desc: { 'ja-JP': 'ドライブのフォルダを作成します。', 'en-US': 'Create a folder of drive.' @@ -11,25 +14,37 @@ export const meta = { requireCredential: true, - kind: 'drive-write' + kind: 'drive-write', + + params: { + name: $.str.optional.pipe(isValidFolderName).note({ + default: 'Untitled', + desc: { + 'ja-JP': 'フォルダ名', + 'en-US': 'Folder name' + } + }), + + parentId: $.type(ID).optional.nullable.note({ + desc: { + 'ja-JP': '親フォルダID', + 'en-US': 'Parent folder ID' + } + }) + } }; export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - // Get 'name' parameter - const [name = '無題のフォルダー', nameErr] = $.str.optional.pipe(isValidFolderName).get(params.name); - if (nameErr) return rej('invalid name param'); - - // Get 'parentId' parameter - const [parentId = null, parentIdErr] = $.type(ID).optional.nullable.get(params.parentId); - if (parentIdErr) return rej('invalid parentId param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // If the parent folder is specified let parent = null; - if (parentId) { + if (ps.parentId) { // Fetch parent folder parent = await DriveFolder .findOne({ - _id: parentId, + _id: ps.parentId, userId: user._id }); @@ -41,7 +56,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = // Create folder const folder = await DriveFolder.insert({ createdAt: new Date(), - name: name, + name: ps.name, parentId: parent !== null ? parent._id : null, userId: user._id }); @@ -52,6 +67,6 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = // Response res(folderObj); - // Publish folder_created event - publishDriveStream(user._id, 'folder_created', folderObj); + // Publish folderCreated event + publishDriveStream(user._id, 'folderCreated', folderObj); }); diff --git a/src/server/api/endpoints/drive/folders/delete.ts b/src/server/api/endpoints/drive/folders/delete.ts new file mode 100644 index 0000000000..ece16ebed4 --- /dev/null +++ b/src/server/api/endpoints/drive/folders/delete.ts @@ -0,0 +1,60 @@ +import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; +import DriveFolder from '../../../../../models/drive-folder'; +import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; +import { publishDriveStream } from '../../../../../stream'; +import DriveFile from '../../../../../models/drive-file'; + +export const meta = { + stability: 'stable', + + desc: { + 'ja-JP': '指定したドライブのフォルダを削除します。', + 'en-US': 'Delete specified folder of drive.' + }, + + requireCredential: true, + + kind: 'drive-write', + + params: { + folderId: $.type(ID).note({ + desc: { + 'ja-JP': '対象のフォルダID', + 'en-US': 'Target folder ID' + } + }) + } +}; + +export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); + + // Get folder + const folder = await DriveFolder + .findOne({ + _id: ps.folderId, + userId: user._id + }); + + if (folder === null) { + return rej('folder-not-found'); + } + + const [childFoldersCount, childFilesCount] = await Promise.all([ + DriveFolder.count({ parentId: folder._id }), + DriveFile.count({ folderId: folder._id }) + ]); + + if (childFoldersCount !== 0 || childFilesCount !== 0) { + return rej('has-child-contents'); + } + + await DriveFolder.remove({ _id: folder._id }); + + // Publish folderCreated event + publishDriveStream(user._id, 'folderDeleted', folder._id); + + res(); +}); diff --git a/src/server/api/endpoints/drive/folders/show.ts b/src/server/api/endpoints/drive/folders/show.ts index bb25bcba3c..f01c75d957 100644 --- a/src/server/api/endpoints/drive/folders/show.ts +++ b/src/server/api/endpoints/drive/folders/show.ts @@ -1,26 +1,38 @@ import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import DriveFolder, { pack } from '../../../../../models/drive-folder'; import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; export const meta = { + stability: 'stable', + desc: { - 'ja-JP': '指定したドライブのフォルダの情報を取得します。' + 'ja-JP': '指定したドライブのフォルダの情報を取得します。', + 'en-US': 'Get specified folder of drive.' }, requireCredential: true, - kind: 'drive-read' + kind: 'drive-read', + + params: { + folderId: $.type(ID).note({ + desc: { + 'ja-JP': '対象のフォルダID', + 'en-US': 'Target folder ID' + } + }) + } }; export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - // Get 'folderId' parameter - const [folderId, folderIdErr] = $.type(ID).get(params.folderId); - if (folderIdErr) return rej('invalid folderId param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Get folder const folder = await DriveFolder .findOne({ - _id: folderId, + _id: ps.folderId, userId: user._id }); diff --git a/src/server/api/endpoints/drive/folders/update.ts b/src/server/api/endpoints/drive/folders/update.ts index 259f373bfc..b041a15920 100644 --- a/src/server/api/endpoints/drive/folders/update.ts +++ b/src/server/api/endpoints/drive/folders/update.ts @@ -2,8 +2,11 @@ import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import DriveFolder, { isValidFolderName, pack } from '../../../../../models/drive-folder'; import { publishDriveStream } from '../../../../../stream'; import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; export const meta = { + stability: 'stable', + desc: { 'ja-JP': '指定したドライブのフォルダの情報を更新します。', 'en-US': 'Update specified folder of drive.' @@ -11,18 +14,40 @@ export const meta = { requireCredential: true, - kind: 'drive-write' + kind: 'drive-write', + + params: { + folderId: $.type(ID).note({ + desc: { + 'ja-JP': '対象のフォルダID', + 'en-US': 'Target folder ID' + } + }), + + name: $.str.optional.pipe(isValidFolderName).note({ + desc: { + 'ja-JP': 'フォルダ名', + 'en-US': 'Folder name' + } + }), + + parentId: $.type(ID).optional.nullable.note({ + desc: { + 'ja-JP': '親フォルダID', + 'en-US': 'Parent folder ID' + } + }) + } }; export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - // Get 'folderId' parameter - const [folderId, folderIdErr] = $.type(ID).get(params.folderId); - if (folderIdErr) return rej('invalid folderId param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Fetch folder const folder = await DriveFolder .findOne({ - _id: folderId, + _id: ps.folderId, userId: user._id }); @@ -30,22 +55,16 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = return rej('folder-not-found'); } - // Get 'name' parameter - const [name, nameErr] = $.str.optional.pipe(isValidFolderName).get(params.name); - if (nameErr) return rej('invalid name param'); - if (name) folder.name = name; + if (ps.name) folder.name = ps.name; - // Get 'parentId' parameter - const [parentId, parentIdErr] = $.type(ID).optional.nullable.get(params.parentId); - if (parentIdErr) return rej('invalid parentId param'); - if (parentId !== undefined) { - if (parentId === null) { + if (ps.parentId !== undefined) { + if (ps.parentId === null) { folder.parentId = null; } else { // Get parent folder const parent = await DriveFolder .findOne({ - _id: parentId, + _id: ps.parentId, userId: user._id }); @@ -96,6 +115,6 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = // Response res(folderObj); - // Publish folder_updated event - publishDriveStream(user._id, 'folder_updated', folderObj); + // Publish folderUpdated event + publishDriveStream(user._id, 'folderUpdated', folderObj); }); diff --git a/src/server/api/endpoints/following/create.ts b/src/server/api/endpoints/following/create.ts index 00aa904f08..372bad0222 100644 --- a/src/server/api/endpoints/following/create.ts +++ b/src/server/api/endpoints/following/create.ts @@ -3,8 +3,11 @@ const ms = require('ms'); import User, { pack, ILocalUser } from '../../../../models/user'; import Following from '../../../../models/following'; import create from '../../../../services/following/create'; +import getParams from '../../get-params'; export const meta = { + stability: 'stable', + desc: { 'ja-JP': '指定したユーザーをフォローします。', 'en-US': 'Follow a user.' @@ -17,24 +20,32 @@ export const meta = { requireCredential: true, - kind: 'following-write' + kind: 'following-write', + + params: { + userId: $.type(ID).note({ + desc: { + 'ja-JP': '対象のユーザーのID', + 'en-US': 'Target user ID' + } + }) + } }; export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - const follower = user; + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); - // Get 'userId' parameter - const [userId, userIdErr] = $.type(ID).get(params.userId); - if (userIdErr) return rej('invalid userId param'); + const follower = user; // 自分自身 - if (user._id.equals(userId)) { + if (user._id.equals(ps.userId)) { return rej('followee is yourself'); } // Get followee const followee = await User.findOne({ - _id: userId + _id: ps.userId }, { fields: { data: false, diff --git a/src/server/api/endpoints/following/delete.ts b/src/server/api/endpoints/following/delete.ts index cdfbf43cd1..0489c1e041 100644 --- a/src/server/api/endpoints/following/delete.ts +++ b/src/server/api/endpoints/following/delete.ts @@ -3,8 +3,11 @@ const ms = require('ms'); import User, { pack, ILocalUser } from '../../../../models/user'; import Following from '../../../../models/following'; import deleteFollowing from '../../../../services/following/delete'; +import getParams from '../../get-params'; export const meta = { + stability: 'stable', + desc: { 'ja-JP': '指定したユーザーのフォローを解除します。', 'en-US': 'Unfollow a user.' @@ -17,24 +20,32 @@ export const meta = { requireCredential: true, - kind: 'following-write' + kind: 'following-write', + + params: { + userId: $.type(ID).note({ + desc: { + 'ja-JP': '対象のユーザーのID', + 'en-US': 'Target user ID' + } + }) + } }; export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - const follower = user; + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); - // Get 'userId' parameter - const [userId, userIdErr] = $.type(ID).get(params.userId); - if (userIdErr) return rej('invalid userId param'); + const follower = user; // Check if the followee is yourself - if (user._id.equals(userId)) { + if (user._id.equals(ps.userId)) { return rej('followee is yourself'); } // Get followee const followee = await User.findOne({ - _id: userId + _id: ps.userId }, { fields: { data: false, diff --git a/src/server/api/endpoints/hashtags/search.ts b/src/server/api/endpoints/hashtags/search.ts index f6fb35b2ff..69cdc62ab8 100644 --- a/src/server/api/endpoints/hashtags/search.ts +++ b/src/server/api/endpoints/hashtags/search.ts @@ -35,7 +35,7 @@ export const meta = { export default (params: any) => new Promise(async (res, rej) => { const [ps, psErr] = getParams(meta, params); - if (psErr) throw psErr; + if (psErr) return rej(psErr); const hashtags = await Hashtag .find({ diff --git a/src/server/api/endpoints/i.ts b/src/server/api/endpoints/i.ts index 5aa2070650..1c488d94c6 100644 --- a/src/server/api/endpoints/i.ts +++ b/src/server/api/endpoints/i.ts @@ -2,6 +2,8 @@ import User, { pack, ILocalUser } from '../../../models/user'; import { IApp } from '../../../models/app'; export const meta = { + stability: 'stable', + desc: { 'ja-JP': '自分のアカウント情報を取得します。' }, diff --git a/src/server/api/endpoints/i/pin.ts b/src/server/api/endpoints/i/pin.ts index bf729ca091..44c7fe77b8 100644 --- a/src/server/api/endpoints/i/pin.ts +++ b/src/server/api/endpoints/i/pin.ts @@ -5,6 +5,8 @@ import { addPinned } from '../../../../services/i/pin'; import getParams from '../../get-params'; export const meta = { + stability: 'stable', + desc: { 'ja-JP': '指定した投稿をピン留めします。' }, @@ -16,7 +18,8 @@ export const meta = { params: { noteId: $.type(ID).note({ desc: { - 'ja-JP': '対象の投稿のID' + 'ja-JP': '対象の投稿のID', + 'en-US': 'Target note ID' } }) } diff --git a/src/server/api/endpoints/i/read_all_unread_notes.ts b/src/server/api/endpoints/i/read_all_unread_notes.ts new file mode 100644 index 0000000000..6a4f72ea29 --- /dev/null +++ b/src/server/api/endpoints/i/read_all_unread_notes.ts @@ -0,0 +1,37 @@ +import User, { ILocalUser } from '../../../../models/user'; +import { publishMainStream } from '../../../../stream'; +import NoteUnread from '../../../../models/note-unread'; + +export const meta = { + desc: { + 'ja-JP': '未読の投稿をすべて既読にします。', + 'en-US': 'Mark all messages as read.' + }, + + requireCredential: true, + + kind: 'account-write', + + params: { + } +}; + +export default async (params: any, user: ILocalUser) => new Promise(async (res, rej) => { + // Remove documents + await NoteUnread.remove({ + userId: user._id + }); + + User.update({ _id: user._id }, { + $set: { + hasUnreadMentions: false, + hasUnreadSpecifiedNotes: false + } + }); + + // 全て既読になったイベントを発行 + publishMainStream(user._id, 'readAllUnreadMentions'); + publishMainStream(user._id, 'readAllUnreadSpecifiedNotes'); + + res(); +}); diff --git a/src/server/api/endpoints/i/unpin.ts b/src/server/api/endpoints/i/unpin.ts index 2a81993e4b..6c20e2771d 100644 --- a/src/server/api/endpoints/i/unpin.ts +++ b/src/server/api/endpoints/i/unpin.ts @@ -5,6 +5,8 @@ import { removePinned } from '../../../../services/i/pin'; import getParams from '../../get-params'; export const meta = { + stability: 'stable', + desc: { 'ja-JP': '指定した投稿のピン留めを解除します。' }, @@ -16,7 +18,8 @@ export const meta = { params: { noteId: $.type(ID).note({ desc: { - 'ja-JP': '対象の投稿のID' + 'ja-JP': '対象の投稿のID', + 'en-US': 'Target note ID' } }) } diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 548ce5cadb..7b8431f0ee 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -67,6 +67,12 @@ export const meta = { } }), + carefulBot: $.bool.optional.note({ + desc: { + 'ja-JP': 'Botからのフォローを承認制にするか' + } + }), + isBot: $.bool.optional.note({ desc: { 'ja-JP': 'Botか否か' @@ -95,7 +101,7 @@ export const meta = { export default async (params: any, user: ILocalUser, app: IApp) => new Promise(async (res, rej) => { const [ps, psErr] = getParams(meta, params); - if (psErr) throw psErr; + if (psErr) return rej(psErr); const isSecure = user != null && app == null; @@ -110,6 +116,7 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a 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.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; diff --git a/src/server/api/endpoints/messaging/messages/read.ts b/src/server/api/endpoints/messaging/messages/read.ts index 581b57579b..1c0bdf5230 100644 --- a/src/server/api/endpoints/messaging/messages/read.ts +++ b/src/server/api/endpoints/messaging/messages/read.ts @@ -26,7 +26,7 @@ export const meta = { export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { const [ps, psErr] = getParams(meta, params); - if (psErr) throw psErr; + if (psErr) return rej(psErr); const message = await Message.findOne({ _id: ps.messageId, diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index c76d7f2e8f..0cd5842312 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -1,6 +1,3 @@ -/** - * Module dependencies - */ import * as os from 'os'; import config from '../../../config'; import Meta from '../../../models/meta'; @@ -9,9 +6,19 @@ import { ILocalUser } from '../../../models/user'; const pkg = require('../../../../package.json'); const client = require('../../../../built/client/meta.json'); -/** - * Show core info - */ +export const meta = { + stability: 'stable', + + desc: { + 'ja-JP': 'インスタンス情報を取得します。', + 'en-US': 'Get the information of this instance.' + }, + + requireCredential: false, + + params: {}, +}; + export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { const meta: any = (await Meta.findOne()) || {}; @@ -28,10 +35,12 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => machine: os.hostname(), os: os.platform(), node: process.version, + cpu: { model: os.cpus()[0].model, cores: os.cpus().length }, + broadcasts: meta.broadcasts || [], disableRegistration: meta.disableRegistration, disableLocalTimeline: meta.disableLocalTimeline, @@ -40,6 +49,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => swPublickey: config.sw ? config.sw.public_key : null, hidedTags: (me && me.isAdmin) ? meta.hidedTags : undefined, bannerUrl: meta.bannerUrl, + maxNoteTextLength: config.maxNoteTextLength, + features: { registration: !meta.disableRegistration, localTimeLine: !meta.disableLocalTimeline, @@ -47,7 +58,8 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => recaptcha: config.recaptcha ? true : false, objectStorage: config.drive && config.drive.storage === 'minio', twitter: config.twitter ? true : false, - serviceWorker: config.sw ? true : false + serviceWorker: config.sw ? true : false, + userRecommendation: config.user_recommendation ? config.user_recommendation : {} } }); }); diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts index d65710d33f..4f5a211240 100644 --- a/src/server/api/endpoints/notes.ts +++ b/src/server/api/endpoints/notes.ts @@ -56,7 +56,7 @@ export const meta = { export default (params: any) => new Promise(async (res, rej) => { const [ps, psErr] = getParams(meta, params); - if (psErr) throw psErr; + if (psErr) return rej(psErr); // Check if both of sinceId and untilId is specified if (ps.sinceId && ps.untilId) { diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 96745132a3..432561da38 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -8,6 +8,8 @@ import { IApp } from '../../../../models/app'; import getParams from '../../get-params'; export const meta = { + stability: 'stable', + desc: { 'ja-JP': '投稿します。' }, diff --git a/src/server/api/endpoints/notes/delete.ts b/src/server/api/endpoints/notes/delete.ts index 2fe36897c0..160d5c4cf6 100644 --- a/src/server/api/endpoints/notes/delete.ts +++ b/src/server/api/endpoints/notes/delete.ts @@ -2,8 +2,11 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note from '../../../../models/note'; import deleteNote from '../../../../services/note/delete'; import User, { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; export const meta = { + stability: 'stable', + desc: { 'ja-JP': '指定した投稿を削除します。', 'en-US': 'Delete a note.' @@ -11,17 +14,25 @@ export const meta = { requireCredential: true, - kind: 'note-write' + kind: 'note-write', + + params: { + noteId: $.type(ID).note({ + desc: { + 'ja-JP': '対象の投稿のID', + 'en-US': 'Target note ID.' + } + }) + } }; export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - // Get 'noteId' parameter - const [noteId, noteIdErr] = $.type(ID).get(params.noteId); - if (noteIdErr) return rej('invalid noteId param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Fetch note const note = await Note.findOne({ - _id: noteId + _id: ps.noteId }); if (note === null) { diff --git a/src/server/api/endpoints/notes/favorites/create.ts b/src/server/api/endpoints/notes/favorites/create.ts index 9aefb701ae..76673e248a 100644 --- a/src/server/api/endpoints/notes/favorites/create.ts +++ b/src/server/api/endpoints/notes/favorites/create.ts @@ -5,6 +5,8 @@ import { ILocalUser } from '../../../../../models/user'; import getParams from '../../../get-params'; export const meta = { + stability: 'stable', + desc: { 'ja-JP': '指定した投稿をお気に入りに登録します。', 'en-US': 'Favorite a note.' @@ -17,7 +19,8 @@ export const meta = { params: { noteId: $.type(ID).note({ desc: { - 'ja-JP': '対象の投稿のID' + 'ja-JP': '対象の投稿のID', + 'en-US': 'Target note ID.' } }) } diff --git a/src/server/api/endpoints/notes/favorites/delete.ts b/src/server/api/endpoints/notes/favorites/delete.ts index e42b24d324..70e1ca8ccf 100644 --- a/src/server/api/endpoints/notes/favorites/delete.ts +++ b/src/server/api/endpoints/notes/favorites/delete.ts @@ -2,8 +2,11 @@ import $ from 'cafy'; import ID from '../../../../../misc/cafy-id'; import Favorite from '../../../../../models/favorite'; import Note from '../../../../../models/note'; import { ILocalUser } from '../../../../../models/user'; +import getParams from '../../../get-params'; export const meta = { + stability: 'stable', + desc: { 'ja-JP': '指定した投稿のお気に入りを解除します。', 'en-US': 'Unfavorite a note.' @@ -11,17 +14,25 @@ export const meta = { requireCredential: true, - kind: 'favorite-write' + kind: 'favorite-write', + + params: { + noteId: $.type(ID).note({ + desc: { + 'ja-JP': '対象の投稿のID', + 'en-US': 'Target note ID.' + } + }) + } }; export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - // Get 'noteId' parameter - const [noteId, noteIdErr] = $.type(ID).get(params.noteId); - if (noteIdErr) return rej('invalid noteId param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Get favoritee const note = await Note.findOne({ - _id: noteId + _id: ps.noteId }); if (note === null) { diff --git a/src/server/api/endpoints/notes/featured.ts b/src/server/api/endpoints/notes/featured.ts new file mode 100644 index 0000000000..363170ead6 --- /dev/null +++ b/src/server/api/endpoints/notes/featured.ts @@ -0,0 +1,49 @@ +import $ from 'cafy'; +import Note from '../../../../models/note'; +import { packMany } from '../../../../models/note'; +import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; + +export const meta = { + desc: { + 'ja-JP': 'Featuredな投稿を取得します。', + 'en-US': 'Get featured notes.' + }, + + requireCredential: false, + + params: { + limit: $.num.optional.range(1, 30).note({ + default: 10, + desc: { + 'ja-JP': '最大数' + } + }) + } +}; + +export default async (params: any, user: ILocalUser) => { + const [ps, psErr] = getParams(meta, params); + if (psErr) throw psErr; + + const day = 1000 * 60 * 60 * 24; + + const notes = await Note + .find({ + createdAt: { + $gt: new Date(Date.now() - day) + }, + deletedAt: null, + visibility: { $in: ['public', 'home'] } + }, { + limit: ps.limit, + sort: { + score: -1 + }, + hint: { + score: -1 + } + }); + + return await packMany(notes, user); +}; diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts index 8362143bb2..8a6c848943 100644 --- a/src/server/api/endpoints/notes/global-timeline.ts +++ b/src/server/api/endpoints/notes/global-timeline.ts @@ -58,6 +58,8 @@ export default async (params: any, user: ILocalUser) => { }; const query = { + deletedAt: null, + // public only visibility: 'public', diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index 14b4432b33..b2ea9c60ac 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -129,6 +129,8 @@ export default async (params: any, user: ILocalUser) => { const query = { $and: [{ + deletedAt: null, + $or: [{ // フォローしている人の投稿 $or: followQuery diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index 8ab07d8ea7..510564129c 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -71,6 +71,8 @@ export default async (params: any, user: ILocalUser) => { }; const query = { + deletedAt: null, + // public only visibility: 'public', diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index 592a94263d..7de0102c6d 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -36,7 +36,7 @@ export const meta = { export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { const [ps, psErr] = getParams(meta, params); - if (psErr) throw psErr; + if (psErr) return rej(psErr); // Check if both of sinceId and untilId is specified if (ps.sinceId && ps.untilId) { @@ -45,6 +45,8 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) = // Construct query const query = { + deletedAt: null, + $or: [{ mentions: user._id }, { diff --git a/src/server/api/endpoints/notes/reactions/create.ts b/src/server/api/endpoints/notes/reactions/create.ts index ec68f065d8..aa9ab07384 100644 --- a/src/server/api/endpoints/notes/reactions/create.ts +++ b/src/server/api/endpoints/notes/reactions/create.ts @@ -6,6 +6,8 @@ import { ILocalUser } from '../../../../../models/user'; import getParams from '../../../get-params'; export const meta = { + stability: 'stable', + desc: { 'ja-JP': '指定した投稿にリアクションします。', 'en-US': 'React to a note.' diff --git a/src/server/api/endpoints/notes/search_by_tag.ts b/src/server/api/endpoints/notes/search_by_tag.ts index d380f27f9c..e7fa15f768 100644 --- a/src/server/api/endpoints/notes/search_by_tag.ts +++ b/src/server/api/endpoints/notes/search_by_tag.ts @@ -111,7 +111,7 @@ export const meta = { export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { const [ps, psErr] = getParams(meta, params); - if (psErr) throw psErr; + if (psErr) return rej(psErr); if (ps.includeUserUsernames != null) { const ids = erase(null, await Promise.all(ps.includeUserUsernames.map(async (username) => { diff --git a/src/server/api/endpoints/notes/show.ts b/src/server/api/endpoints/notes/show.ts index 3f94eeede5..e84a948c97 100644 --- a/src/server/api/endpoints/notes/show.ts +++ b/src/server/api/endpoints/notes/show.ts @@ -1,18 +1,35 @@ import $ from 'cafy'; import ID from '../../../../misc/cafy-id'; import Note, { pack } from '../../../../models/note'; import { ILocalUser } from '../../../../models/user'; +import getParams from '../../get-params'; + +export const meta = { + stability: 'stable', + + desc: { + 'ja-JP': '指定した投稿を取得します。', + 'en-US': 'Get a note.' + }, + + requireCredential: false, + + params: { + noteId: $.type(ID).note({ + desc: { + 'ja-JP': '対象の投稿のID', + 'en-US': 'Target note ID.' + } + }) + } +}; -/** - * Show a note - */ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { - // Get 'noteId' parameter - const [noteId, noteIdErr] = $.type(ID).get(params.noteId); - if (noteIdErr) return rej('invalid noteId param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); // Get note const note = await Note.findOne({ - _id: noteId + _id: ps.noteId }); if (note === null) { diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index 44a504eb18..31a4978407 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -132,6 +132,8 @@ export default async (params: any, user: ILocalUser) => { const query = { $and: [{ + deletedAt: null, + // フォローしている人の投稿 $or: followQuery, diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts index 6758b4eb73..7dddc4834e 100644 --- a/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/src/server/api/endpoints/notes/user-list-timeline.ts @@ -137,6 +137,8 @@ export default async (params: any, user: ILocalUser) => { const query = { $and: [{ + deletedAt: null, + // リストに入っている人のタイムラインへの投稿 $or: listQuery, diff --git a/src/server/api/endpoints/users/lists/update.ts b/src/server/api/endpoints/users/lists/update.ts index e6577eca4f..e39f66fb5b 100644 --- a/src/server/api/endpoints/users/lists/update.ts +++ b/src/server/api/endpoints/users/lists/update.ts @@ -32,7 +32,7 @@ export const meta = { export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => { const [ps, psErr] = getParams(meta, params); - if (psErr) throw psErr; + if (psErr) return rej(psErr); // Fetch the list const userList = await UserList.findOne({ diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index 1bfe832c51..8605a32dee 100644 --- a/src/server/api/endpoints/users/notes.ts +++ b/src/server/api/endpoints/users/notes.ts @@ -99,12 +99,18 @@ export const meta = { 'ja-JP': 'true にすると、ファイルが添付された投稿だけ取得します (このパラメータは廃止予定です。代わりに withFiles を使ってください。)' } }), + + fileType: $.arr($.str).optional.note({ + desc: { + 'ja-JP': '指定された種類のファイルが添付された投稿のみを取得します' + } + }), } }; export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { const [ps, psErr] = getParams(meta, params); - if (psErr) throw psErr; + if (psErr) return rej(psErr); if (ps.userId === undefined && ps.username === undefined) { return rej('userId or username is required'); @@ -136,7 +142,9 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => }; const query = { - userId: user._id + deletedAt: null, + userId: user._id, + visibility: { $in: ['public', 'home'] } } as any; if (ps.sinceId) { @@ -171,6 +179,14 @@ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => $ne: [] }; } + + if (ps.fileType) { + query.fileIds = { $exists: true, $ne: [] }; + + query['_files.contentType'] = { + $in: ps.fileType + }; + } //#endregion // Issue query diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts index e0a5cb9e36..df85343f0f 100644 --- a/src/server/api/endpoints/users/recommendation.ts +++ b/src/server/api/endpoints/users/recommendation.ts @@ -3,6 +3,8 @@ import $ from 'cafy'; import User, { pack, ILocalUser } from '../../../../models/user'; import { getFriendIds } from '../../common/get-friends'; import Mute from '../../../../models/mute'; +import * as request from 'request'; +import config from '../../../../config'; export const meta = { desc: { @@ -15,44 +17,70 @@ export const meta = { }; export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); - if (limitErr) return rej('invalid limit param'); + if (config.user_recommendation && config.user_recommendation.external) { + const userName = me.username; + const hostName = config.hostname; + const limit = params.limit; + const offset = params.offset; + const timeout = config.user_recommendation.timeout; + const engine = config.user_recommendation.engine; + const url = engine + .replace('{{host}}', hostName) + .replace('{{user}}', userName) + .replace('{{limit}}', limit) + .replace('{{offset}}', offset); - // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); - if (offsetErr) return rej('invalid offset param'); + request({ + url: url, + proxy: config.proxy, + timeout: timeout, + json: true, + followRedirect: true, + followAllRedirects: true + }, (error: any, response: any, body: any) => { + if (!error && response.statusCode == 200) { + res(body); + } else { + res([]); + } + }); + } else { + // Get 'limit' parameter + const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); + if (limitErr) return rej('invalid limit param'); + + // Get 'offset' parameter + const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); + if (offsetErr) return rej('invalid offset param'); - // ID list of the user itself and other users who the user follows - const followingIds = await getFriendIds(me._id); + // ID list of the user itself and other users who the user follows + const followingIds = await getFriendIds(me._id); - // ミュートしているユーザーを取得 - const mutedUserIds = (await Mute.find({ - muterId: me._id - })).map(m => m.muteeId); + // ミュートしているユーザーを取得 + const mutedUserIds = (await Mute.find({ + muterId: me._id + })).map(m => m.muteeId); - const users = await User - .find({ - _id: { - $nin: followingIds.concat(mutedUserIds) - }, - isLocked: false, - $or: [{ + const users = await User + .find({ + _id: { + $nin: followingIds.concat(mutedUserIds) + }, + isLocked: { $ne: true }, lastUsedAt: { $gte: new Date(Date.now() - ms('7days')) - } - }, { + }, host: null - }] - }, { - limit: limit, - skip: offset, - sort: { - followersCount: -1 - } - }); + }, { + limit: limit, + skip: offset, + sort: { + followersCount: -1 + } + }); - // Serialize - res(await Promise.all(users.map(async user => - await pack(user, me, { detail: true })))); + // Serialize + res(await Promise.all(users.map(async user => + await pack(user, me, { detail: true })))); + } }); |