summaryrefslogtreecommitdiff
path: root/src/server/api/endpoints
diff options
context:
space:
mode:
Diffstat (limited to 'src/server/api/endpoints')
-rw-r--r--src/server/api/endpoints/admin/accounts/create.ts33
-rw-r--r--src/server/api/endpoints/admin/announcements/create.ts36
-rw-r--r--src/server/api/endpoints/admin/announcements/delete.ts34
-rw-r--r--src/server/api/endpoints/admin/announcements/list.ts41
-rw-r--r--src/server/api/endpoints/admin/announcements/update.ts48
-rw-r--r--src/server/api/endpoints/admin/emoji/list-remote.ts62
-rw-r--r--src/server/api/endpoints/admin/emoji/list.ts32
-rw-r--r--src/server/api/endpoints/admin/queue/deliver-delayed.ts31
-rw-r--r--src/server/api/endpoints/admin/queue/inbox-delayed.ts31
-rw-r--r--src/server/api/endpoints/admin/server-info.ts45
-rw-r--r--src/server/api/endpoints/admin/update-meta.ts30
-rw-r--r--src/server/api/endpoints/announcements.ts42
-rw-r--r--src/server/api/endpoints/antennas/create.ts92
-rw-r--r--src/server/api/endpoints/antennas/delete.ts40
-rw-r--r--src/server/api/endpoints/antennas/list.ts18
-rw-r--r--src/server/api/endpoints/antennas/notes.ts72
-rw-r--r--src/server/api/endpoints/antennas/show.ts41
-rw-r--r--src/server/api/endpoints/antennas/update.ts108
-rw-r--r--src/server/api/endpoints/clips/create.ts29
-rw-r--r--src/server/api/endpoints/clips/delete.ts40
-rw-r--r--src/server/api/endpoints/clips/list.ts18
-rw-r--r--src/server/api/endpoints/clips/notes.ts67
-rw-r--r--src/server/api/endpoints/clips/show.ts41
-rw-r--r--src/server/api/endpoints/clips/update.ts49
-rw-r--r--src/server/api/endpoints/federation/followers.ts51
-rw-r--r--src/server/api/endpoints/federation/following.ts51
-rw-r--r--src/server/api/endpoints/federation/instances.ts56
-rw-r--r--src/server/api/endpoints/federation/users.ts51
-rw-r--r--src/server/api/endpoints/i/notifications.ts4
-rw-r--r--src/server/api/endpoints/i/read-announcement.ts60
-rw-r--r--src/server/api/endpoints/meta.ts27
-rw-r--r--src/server/api/endpoints/notes/create.ts18
-rw-r--r--src/server/api/endpoints/notes/featured.ts19
-rw-r--r--src/server/api/endpoints/notes/search.ts133
-rw-r--r--src/server/api/endpoints/users/search-by-username-and-host.ts101
35 files changed, 1500 insertions, 151 deletions
diff --git a/src/server/api/endpoints/admin/accounts/create.ts b/src/server/api/endpoints/admin/accounts/create.ts
new file mode 100644
index 0000000000..ac80b579b7
--- /dev/null
+++ b/src/server/api/endpoints/admin/accounts/create.ts
@@ -0,0 +1,33 @@
+import define from '../../../define';
+import { Users } from '../../../../../models';
+import { signup } from '../../../common/signup';
+
+export const meta = {
+ tags: ['admin'],
+
+ params: {
+ username: {
+ validator: Users.validateLocalUsername,
+ },
+
+ password: {
+ validator: Users.validatePassword,
+ }
+ }
+};
+
+export default define(meta, async (ps, me) => {
+ const noUsers = (await Users.count({})) === 0;
+ if (!noUsers && me == null) throw new Error('access denied');
+
+ const { account, secret } = await signup(ps.username, ps.password);
+
+ const res = await Users.pack(account, account, {
+ detail: true,
+ includeSecrets: true
+ });
+
+ (res as any).token = secret;
+
+ return res;
+});
diff --git a/src/server/api/endpoints/admin/announcements/create.ts b/src/server/api/endpoints/admin/announcements/create.ts
new file mode 100644
index 0000000000..c1d48a7d38
--- /dev/null
+++ b/src/server/api/endpoints/admin/announcements/create.ts
@@ -0,0 +1,36 @@
+import $ from 'cafy';
+import define from '../../../define';
+import { Announcements } from '../../../../../models';
+import { genId } from '../../../../../misc/gen-id';
+
+export const meta = {
+ tags: ['admin'],
+
+ requireCredential: true,
+ requireModerator: true,
+
+ params: {
+ title: {
+ validator: $.str.min(1)
+ },
+ text: {
+ validator: $.str.min(1)
+ },
+ imageUrl: {
+ validator: $.nullable.str.min(1)
+ }
+ }
+};
+
+export default define(meta, async (ps) => {
+ const announcement = await Announcements.save({
+ id: genId(),
+ createdAt: new Date(),
+ updatedAt: null,
+ title: ps.title,
+ text: ps.text,
+ imageUrl: ps.imageUrl,
+ });
+
+ return announcement;
+});
diff --git a/src/server/api/endpoints/admin/announcements/delete.ts b/src/server/api/endpoints/admin/announcements/delete.ts
new file mode 100644
index 0000000000..284b4bf549
--- /dev/null
+++ b/src/server/api/endpoints/admin/announcements/delete.ts
@@ -0,0 +1,34 @@
+import $ from 'cafy';
+import define from '../../../define';
+import { ID } from '../../../../../misc/cafy-id';
+import { Announcements } from '../../../../../models';
+import { ApiError } from '../../../error';
+
+export const meta = {
+ tags: ['admin'],
+
+ requireCredential: true,
+ requireModerator: true,
+
+ params: {
+ id: {
+ validator: $.type(ID)
+ }
+ },
+
+ errors: {
+ noSuchAnnouncement: {
+ message: 'No such announcement.',
+ code: 'NO_SUCH_ANNOUNCEMENT',
+ id: 'ecad8040-a276-4e85-bda9-015a708d291e'
+ }
+ }
+};
+
+export default define(meta, async (ps, me) => {
+ const announcement = await Announcements.findOne(ps.id);
+
+ if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
+
+ await Announcements.delete(announcement.id);
+});
diff --git a/src/server/api/endpoints/admin/announcements/list.ts b/src/server/api/endpoints/admin/announcements/list.ts
new file mode 100644
index 0000000000..f4e622144e
--- /dev/null
+++ b/src/server/api/endpoints/admin/announcements/list.ts
@@ -0,0 +1,41 @@
+import $ from 'cafy';
+import { ID } from '../../../../../misc/cafy-id';
+import define from '../../../define';
+import { Announcements, AnnouncementReads } from '../../../../../models';
+import { makePaginationQuery } from '../../../common/make-pagination-query';
+
+export const meta = {
+ tags: ['admin'],
+
+ requireCredential: true,
+ requireModerator: true,
+
+ params: {
+ limit: {
+ validator: $.optional.num.range(1, 100),
+ default: 10
+ },
+
+ sinceId: {
+ validator: $.optional.type(ID),
+ },
+
+ untilId: {
+ validator: $.optional.type(ID),
+ },
+ }
+};
+
+export default define(meta, async (ps) => {
+ const query = makePaginationQuery(Announcements.createQueryBuilder('announcement'), ps.sinceId, ps.untilId);
+
+ const announcements = await query.take(ps.limit!).getMany();
+
+ for (const announcement of announcements) {
+ (announcement as any).reads = await AnnouncementReads.count({
+ announcementId: announcement.id
+ });
+ }
+
+ return announcements;
+});
diff --git a/src/server/api/endpoints/admin/announcements/update.ts b/src/server/api/endpoints/admin/announcements/update.ts
new file mode 100644
index 0000000000..b65c3a4f93
--- /dev/null
+++ b/src/server/api/endpoints/admin/announcements/update.ts
@@ -0,0 +1,48 @@
+import $ from 'cafy';
+import define from '../../../define';
+import { ID } from '../../../../../misc/cafy-id';
+import { Announcements } from '../../../../../models';
+import { ApiError } from '../../../error';
+
+export const meta = {
+ tags: ['admin'],
+
+ requireCredential: true,
+ requireModerator: true,
+
+ params: {
+ id: {
+ validator: $.type(ID)
+ },
+ title: {
+ validator: $.str.min(1)
+ },
+ text: {
+ validator: $.str.min(1)
+ },
+ imageUrl: {
+ validator: $.nullable.str.min(1)
+ }
+ },
+
+ errors: {
+ noSuchAnnouncement: {
+ message: 'No such announcement.',
+ code: 'NO_SUCH_ANNOUNCEMENT',
+ id: 'd3aae5a7-6372-4cb4-b61c-f511ffc2d7cc'
+ }
+ }
+};
+
+export default define(meta, async (ps, me) => {
+ const announcement = await Announcements.findOne(ps.id);
+
+ if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
+
+ await Announcements.update(announcement.id, {
+ updatedAt: new Date(),
+ title: ps.title,
+ text: ps.text,
+ imageUrl: ps.imageUrl,
+ });
+});
diff --git a/src/server/api/endpoints/admin/emoji/list-remote.ts b/src/server/api/endpoints/admin/emoji/list-remote.ts
new file mode 100644
index 0000000000..0a3e74c333
--- /dev/null
+++ b/src/server/api/endpoints/admin/emoji/list-remote.ts
@@ -0,0 +1,62 @@
+import $ from 'cafy';
+import define from '../../../define';
+import { Emojis } from '../../../../../models';
+import { toPuny } from '../../../../../misc/convert-host';
+import { makePaginationQuery } from '../../../common/make-pagination-query';
+import { ID } from '../../../../../misc/cafy-id';
+
+export const meta = {
+ desc: {
+ 'ja-JP': 'カスタム絵文字を取得します。'
+ },
+
+ tags: ['admin'],
+
+ requireCredential: true,
+ requireModerator: true,
+
+ params: {
+ host: {
+ validator: $.optional.nullable.str,
+ default: null as any
+ },
+
+ limit: {
+ validator: $.optional.num.range(1, 100),
+ default: 10
+ },
+
+ sinceId: {
+ validator: $.optional.type(ID),
+ },
+
+ untilId: {
+ validator: $.optional.type(ID),
+ }
+ }
+};
+
+export default define(meta, async (ps) => {
+ const q = makePaginationQuery(Emojis.createQueryBuilder('emoji'), ps.sinceId, ps.untilId);
+
+ if (ps.host == null) {
+ q.andWhere(`emoji.host IS NOT NULL`);
+ } else {
+ q.andWhere(`emoji.host = :host`, { host: toPuny(ps.host) });
+ }
+
+ const emojis = await q
+ .orderBy('emoji.category', 'ASC')
+ .orderBy('emoji.name', 'ASC')
+ .take(ps.limit!)
+ .getMany();
+
+ return emojis.map(e => ({
+ id: e.id,
+ name: e.name,
+ category: e.category,
+ aliases: e.aliases,
+ host: e.host,
+ url: e.url
+ }));
+});
diff --git a/src/server/api/endpoints/admin/emoji/list.ts b/src/server/api/endpoints/admin/emoji/list.ts
index d2a5e7df0d..d525a659c0 100644
--- a/src/server/api/endpoints/admin/emoji/list.ts
+++ b/src/server/api/endpoints/admin/emoji/list.ts
@@ -1,7 +1,8 @@
import $ from 'cafy';
import define from '../../../define';
import { Emojis } from '../../../../../models';
-import { toPunyNullable } from '../../../../../misc/convert-host';
+import { makePaginationQuery } from '../../../common/make-pagination-query';
+import { ID } from '../../../../../misc/cafy-id';
export const meta = {
desc: {
@@ -14,23 +15,28 @@ export const meta = {
requireModerator: true,
params: {
- host: {
- validator: $.optional.nullable.str,
- default: null as any
+ limit: {
+ validator: $.optional.num.range(1, 100),
+ default: 10
+ },
+
+ sinceId: {
+ validator: $.optional.type(ID),
+ },
+
+ untilId: {
+ validator: $.optional.type(ID),
}
}
};
export default define(meta, async (ps) => {
- const emojis = await Emojis.find({
- where: {
- host: toPunyNullable(ps.host)
- },
- order: {
- category: 'ASC',
- name: 'ASC'
- }
- });
+ const emojis = await makePaginationQuery(Emojis.createQueryBuilder('emoji'), ps.sinceId, ps.untilId)
+ .andWhere(`emoji.host IS NULL`)
+ .orderBy('emoji.category', 'ASC')
+ .orderBy('emoji.name', 'ASC')
+ .take(ps.limit!)
+ .getMany();
return emojis.map(e => ({
id: e.id,
diff --git a/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/src/server/api/endpoints/admin/queue/deliver-delayed.ts
new file mode 100644
index 0000000000..d33837c099
--- /dev/null
+++ b/src/server/api/endpoints/admin/queue/deliver-delayed.ts
@@ -0,0 +1,31 @@
+import define from '../../../define';
+import { deliverQueue } from '../../../../../queue';
+
+export const meta = {
+ tags: ['admin'],
+
+ requireCredential: true,
+ requireModerator: true,
+
+ params: {
+ }
+};
+
+export default define(meta, async (ps) => {
+ const jobs = await deliverQueue.getJobs(['delayed']);
+
+ const res = [] as [string, number][];
+
+ for (const job of jobs) {
+ const host = new URL(job.data.to).host;
+ if (res.find(x => x[0] === host)) {
+ res.find(x => x[0] === host)![1]++;
+ } else {
+ res.push([host, 1]);
+ }
+ }
+
+ res.sort((a, b) => b[1] - a[1]);
+
+ return res;
+});
diff --git a/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/src/server/api/endpoints/admin/queue/inbox-delayed.ts
new file mode 100644
index 0000000000..643e22f10d
--- /dev/null
+++ b/src/server/api/endpoints/admin/queue/inbox-delayed.ts
@@ -0,0 +1,31 @@
+import define from '../../../define';
+import { inboxQueue } from '../../../../../queue';
+
+export const meta = {
+ tags: ['admin'],
+
+ requireCredential: true,
+ requireModerator: true,
+
+ params: {
+ }
+};
+
+export default define(meta, async (ps) => {
+ const jobs = await inboxQueue.getJobs(['delayed']);
+
+ const res = [] as [string, number][];
+
+ for (const job of jobs) {
+ const host = new URL(job.data.signature.keyId).host;
+ if (res.find(x => x[0] === host)) {
+ res.find(x => x[0] === host)![1]++;
+ } else {
+ res.push([host, 1]);
+ }
+ }
+
+ res.sort((a, b) => b[1] - a[1]);
+
+ return res;
+});
diff --git a/src/server/api/endpoints/admin/server-info.ts b/src/server/api/endpoints/admin/server-info.ts
new file mode 100644
index 0000000000..f51040a2c8
--- /dev/null
+++ b/src/server/api/endpoints/admin/server-info.ts
@@ -0,0 +1,45 @@
+import * as os from 'os';
+import * as si from 'systeminformation';
+import { getConnection } from 'typeorm';
+import define from '../../define';
+import redis from '../../../../db/redis';
+
+export const meta = {
+ requireCredential: false,
+
+ desc: {
+ },
+
+ tags: ['meta'],
+
+ params: {
+ },
+};
+
+export default define(meta, async () => {
+ const memStats = await si.mem();
+ const fsStats = await si.fsSize();
+ const netInterface = await si.networkInterfaceDefault();
+
+ return {
+ machine: os.hostname(),
+ os: os.platform(),
+ node: process.version,
+ psql: await getConnection().query('SHOW server_version').then(x => x[0].server_version),
+ redis: redis.server_info.redis_version,
+ cpu: {
+ model: os.cpus()[0].model,
+ cores: os.cpus().length
+ },
+ mem: {
+ total: memStats.total
+ },
+ fs: {
+ total: fsStats[0].size,
+ used: fsStats[0].used,
+ },
+ net: {
+ interface: netInterface
+ }
+ };
+});
diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts
index bc37228d0a..65650f1295 100644
--- a/src/server/api/endpoints/admin/update-meta.ts
+++ b/src/server/api/endpoints/admin/update-meta.ts
@@ -13,16 +13,9 @@ export const meta = {
tags: ['admin'],
requireCredential: true,
- requireModerator: true,
+ requireAdmin: true,
params: {
- announcements: {
- validator: $.optional.nullable.arr($.obj()),
- desc: {
- 'ja-JP': 'お知らせ'
- }
- },
-
disableRegistration: {
validator: $.optional.nullable.bool,
desc: {
@@ -44,13 +37,6 @@ export const meta = {
}
},
- enableEmojiReaction: {
- validator: $.optional.nullable.bool,
- desc: {
- 'ja-JP': '絵文字リアクションを有効にするか否か'
- }
- },
-
useStarForReactionFallback: {
validator: $.optional.nullable.bool,
desc: {
@@ -347,7 +333,7 @@ export const meta = {
}
},
- ToSUrl: {
+ tosUrl: {
validator: $.optional.nullable.str,
desc: {
'ja-JP': '利用規約のURL'
@@ -413,10 +399,6 @@ export const meta = {
export default define(meta, async (ps, me) => {
const set = {} as Partial<Meta>;
- if (ps.announcements) {
- set.announcements = ps.announcements;
- }
-
if (typeof ps.disableRegistration === 'boolean') {
set.disableRegistration = ps.disableRegistration;
}
@@ -429,10 +411,6 @@ export default define(meta, async (ps, me) => {
set.disableGlobalTimeline = ps.disableGlobalTimeline;
}
- if (typeof ps.enableEmojiReaction === 'boolean') {
- set.enableEmojiReaction = ps.enableEmojiReaction;
- }
-
if (typeof ps.useStarForReactionFallback === 'boolean') {
set.useStarForReactionFallback = ps.useStarForReactionFallback;
}
@@ -601,8 +579,8 @@ export default define(meta, async (ps, me) => {
set.swPrivateKey = ps.swPrivateKey;
}
- if (ps.ToSUrl !== undefined) {
- set.ToSUrl = ps.ToSUrl;
+ if (ps.tosUrl !== undefined) {
+ set.ToSUrl = ps.tosUrl;
}
if (ps.repositoryUrl !== undefined) {
diff --git a/src/server/api/endpoints/announcements.ts b/src/server/api/endpoints/announcements.ts
new file mode 100644
index 0000000000..c6050d6092
--- /dev/null
+++ b/src/server/api/endpoints/announcements.ts
@@ -0,0 +1,42 @@
+import $ from 'cafy';
+import { ID } from '../../../misc/cafy-id';
+import define from '../define';
+import { Announcements, AnnouncementReads } from '../../../models';
+import { makePaginationQuery } from '../common/make-pagination-query';
+
+export const meta = {
+ requireCredential: false,
+
+ params: {
+ limit: {
+ validator: $.optional.num.range(1, 100),
+ default: 10
+ },
+
+ sinceId: {
+ validator: $.optional.type(ID),
+ },
+
+ untilId: {
+ validator: $.optional.type(ID),
+ },
+ }
+};
+
+export default define(meta, async (ps, user) => {
+ const query = makePaginationQuery(Announcements.createQueryBuilder('announcement'), ps.sinceId, ps.untilId);
+
+ const announcements = await query.take(ps.limit!).getMany();
+
+ if (user) {
+ const reads = (await AnnouncementReads.find({
+ userId: user.id
+ })).map(x => x.announcementId);
+
+ for (const announcement of announcements) {
+ (announcement as any).isRead = reads.includes(announcement.id);
+ }
+ }
+
+ return announcements;
+});
diff --git a/src/server/api/endpoints/antennas/create.ts b/src/server/api/endpoints/antennas/create.ts
new file mode 100644
index 0000000000..0e00eda1a4
--- /dev/null
+++ b/src/server/api/endpoints/antennas/create.ts
@@ -0,0 +1,92 @@
+import $ from 'cafy';
+import define from '../../define';
+import { genId } from '../../../../misc/gen-id';
+import { Antennas, UserLists } from '../../../../models';
+import { ID } from '../../../../misc/cafy-id';
+import { ApiError } from '../../error';
+
+export const meta = {
+ tags: ['antennas'],
+
+ requireCredential: true,
+
+ kind: 'write:account',
+
+ params: {
+ name: {
+ validator: $.str.range(1, 100)
+ },
+
+ src: {
+ validator: $.str.or(['home', 'all', 'users', 'list'])
+ },
+
+ userListId: {
+ validator: $.nullable.optional.type(ID),
+ },
+
+ keywords: {
+ validator: $.arr($.arr($.str))
+ },
+
+ users: {
+ validator: $.arr($.str)
+ },
+
+ caseSensitive: {
+ validator: $.bool
+ },
+
+ withReplies: {
+ validator: $.bool
+ },
+
+ withFile: {
+ validator: $.bool
+ },
+
+ notify: {
+ validator: $.bool
+ }
+ },
+
+ errors: {
+ noSuchUserList: {
+ message: 'No such user list.',
+ code: 'NO_SUCH_USER_LIST',
+ id: '95063e93-a283-4b8b-9aa5-bcdb8df69a7f'
+ }
+ }
+};
+
+export default define(meta, async (ps, user) => {
+ let userList;
+
+ if (ps.src === 'list') {
+ userList = await UserLists.findOne({
+ id: ps.userListId,
+ userId: user.id,
+ });
+
+ if (userList == null) {
+ throw new ApiError(meta.errors.noSuchUserList);
+ }
+ }
+
+ const antenna = await Antennas.save({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ name: ps.name,
+ src: ps.src,
+ userListId: userList ? userList.id : null,
+ keywords: ps.keywords,
+ users: ps.users,
+ caseSensitive: ps.caseSensitive,
+ withReplies: ps.withReplies,
+ withFile: ps.withFile,
+ notify: ps.notify,
+ });
+
+ return await Antennas.pack(antenna);
+});
diff --git a/src/server/api/endpoints/antennas/delete.ts b/src/server/api/endpoints/antennas/delete.ts
new file mode 100644
index 0000000000..6bf9165aed
--- /dev/null
+++ b/src/server/api/endpoints/antennas/delete.ts
@@ -0,0 +1,40 @@
+import $ from 'cafy';
+import { ID } from '../../../../misc/cafy-id';
+import define from '../../define';
+import { ApiError } from '../../error';
+import { Antennas } from '../../../../models';
+
+export const meta = {
+ tags: ['antennas'],
+
+ requireCredential: true,
+
+ kind: 'write:account',
+
+ params: {
+ antennaId: {
+ validator: $.type(ID),
+ }
+ },
+
+ errors: {
+ noSuchAntenna: {
+ message: 'No such antenna.',
+ code: 'NO_SUCH_ANTENNA',
+ id: 'b34dcf9d-348f-44bb-99d0-6c9314cfe2df'
+ }
+ }
+};
+
+export default define(meta, async (ps, user) => {
+ const antenna = await Antennas.findOne({
+ id: ps.antennaId,
+ userId: user.id
+ });
+
+ if (antenna == null) {
+ throw new ApiError(meta.errors.noSuchAntenna);
+ }
+
+ await Antennas.delete(antenna.id);
+});
diff --git a/src/server/api/endpoints/antennas/list.ts b/src/server/api/endpoints/antennas/list.ts
new file mode 100644
index 0000000000..3f9deff32f
--- /dev/null
+++ b/src/server/api/endpoints/antennas/list.ts
@@ -0,0 +1,18 @@
+import define from '../../define';
+import { Antennas } from '../../../../models';
+
+export const meta = {
+ tags: ['antennas', 'account'],
+
+ requireCredential: true,
+
+ kind: 'read:account',
+};
+
+export default define(meta, async (ps, me) => {
+ const antennas = await Antennas.find({
+ userId: me.id,
+ });
+
+ return await Promise.all(antennas.map(x => Antennas.pack(x)));
+});
diff --git a/src/server/api/endpoints/antennas/notes.ts b/src/server/api/endpoints/antennas/notes.ts
new file mode 100644
index 0000000000..b4c8e7e698
--- /dev/null
+++ b/src/server/api/endpoints/antennas/notes.ts
@@ -0,0 +1,72 @@
+import $ from 'cafy';
+import { ID } from '../../../../misc/cafy-id';
+import define from '../../define';
+import { Antennas, Notes, AntennaNotes } from '../../../../models';
+import { makePaginationQuery } from '../../common/make-pagination-query';
+import { generateVisibilityQuery } from '../../common/generate-visibility-query';
+import { generateMuteQuery } from '../../common/generate-mute-query';
+import { ApiError } from '../../error';
+
+export const meta = {
+ tags: ['account', 'notes', 'antennas'],
+
+ requireCredential: true,
+
+ kind: 'read:account',
+
+ params: {
+ antennaId: {
+ validator: $.type(ID),
+ },
+
+ limit: {
+ validator: $.optional.num.range(1, 100),
+ default: 10
+ },
+
+ sinceId: {
+ validator: $.optional.type(ID),
+ },
+
+ untilId: {
+ validator: $.optional.type(ID),
+ },
+ },
+
+ errors: {
+ noSuchAntenna: {
+ message: 'No such antenna.',
+ code: 'NO_SUCH_ANTENNA',
+ id: '850926e0-fd3b-49b6-b69a-b28a5dbd82fe'
+ }
+ }
+};
+
+export default define(meta, async (ps, user) => {
+ const antenna = await Antennas.findOne({
+ id: ps.antennaId,
+ userId: user.id
+ });
+
+ if (antenna == null) {
+ throw new ApiError(meta.errors.noSuchAntenna);
+ }
+
+ const antennaQuery = AntennaNotes.createQueryBuilder('joining')
+ .select('joining.noteId')
+ .where('joining.antennaId = :antennaId', { antennaId: antenna.id });
+
+ const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
+ .andWhere(`note.id IN (${ antennaQuery.getQuery() })`)
+ .leftJoinAndSelect('note.user', 'user')
+ .setParameters(antennaQuery.getParameters());
+
+ generateVisibilityQuery(query, user);
+ generateMuteQuery(query, user);
+
+ const notes = await query
+ .take(ps.limit!)
+ .getMany();
+
+ return await Notes.packMany(notes, user);
+});
diff --git a/src/server/api/endpoints/antennas/show.ts b/src/server/api/endpoints/antennas/show.ts
new file mode 100644
index 0000000000..dd87de1dce
--- /dev/null
+++ b/src/server/api/endpoints/antennas/show.ts
@@ -0,0 +1,41 @@
+import $ from 'cafy';
+import { ID } from '../../../../misc/cafy-id';
+import define from '../../define';
+import { ApiError } from '../../error';
+import { Antennas } from '../../../../models';
+
+export const meta = {
+ tags: ['antennas', 'account'],
+
+ requireCredential: true,
+
+ kind: 'read:account',
+
+ params: {
+ antennaId: {
+ validator: $.type(ID),
+ },
+ },
+
+ errors: {
+ noSuchAntenna: {
+ message: 'No such antenna.',
+ code: 'NO_SUCH_ANTENNA',
+ id: 'c06569fb-b025-4f23-b22d-1fcd20d2816b'
+ },
+ }
+};
+
+export default define(meta, async (ps, me) => {
+ // Fetch the antenna
+ const antenna = await Antennas.findOne({
+ id: ps.antennaId,
+ userId: me.id,
+ });
+
+ if (antenna == null) {
+ throw new ApiError(meta.errors.noSuchAntenna);
+ }
+
+ return await Antennas.pack(antenna);
+});
diff --git a/src/server/api/endpoints/antennas/update.ts b/src/server/api/endpoints/antennas/update.ts
new file mode 100644
index 0000000000..28875d0f08
--- /dev/null
+++ b/src/server/api/endpoints/antennas/update.ts
@@ -0,0 +1,108 @@
+import $ from 'cafy';
+import { ID } from '../../../../misc/cafy-id';
+import define from '../../define';
+import { ApiError } from '../../error';
+import { Antennas, UserLists } from '../../../../models';
+
+export const meta = {
+ tags: ['antennas'],
+
+ requireCredential: true,
+
+ kind: 'write:account',
+
+ params: {
+ antennaId: {
+ validator: $.type(ID),
+ },
+
+ name: {
+ validator: $.str.range(1, 100)
+ },
+
+ src: {
+ validator: $.str.or(['home', 'all', 'users', 'list'])
+ },
+
+ userListId: {
+ validator: $.nullable.optional.type(ID),
+ },
+
+ keywords: {
+ validator: $.arr($.arr($.str))
+ },
+
+ users: {
+ validator: $.arr($.str)
+ },
+
+ caseSensitive: {
+ validator: $.bool
+ },
+
+ withReplies: {
+ validator: $.bool
+ },
+
+ withFile: {
+ validator: $.bool
+ },
+
+ notify: {
+ validator: $.bool
+ }
+ },
+
+ errors: {
+ noSuchAntenna: {
+ message: 'No such antenna.',
+ code: 'NO_SUCH_ANTENNA',
+ id: '10c673ac-8852-48eb-aa1f-f5b67f069290'
+ },
+
+ noSuchUserList: {
+ message: 'No such user list.',
+ code: 'NO_SUCH_USER_LIST',
+ id: '1c6b35c9-943e-48c2-81e4-2844989407f7'
+ }
+ }
+};
+
+export default define(meta, async (ps, user) => {
+ // Fetch the antenna
+ const antenna = await Antennas.findOne({
+ id: ps.antennaId,
+ userId: user.id
+ });
+
+ if (antenna == null) {
+ throw new ApiError(meta.errors.noSuchAntenna);
+ }
+
+ let userList;
+
+ if (ps.src === 'list') {
+ userList = await UserLists.findOne({
+ id: ps.userListId,
+ userId: user.id,
+ });
+
+ if (userList == null) {
+ throw new ApiError(meta.errors.noSuchUserList);
+ }
+ }
+
+ await Antennas.update(antenna.id, {
+ name: ps.name,
+ src: ps.src,
+ userListId: userList ? userList.id : null,
+ keywords: ps.keywords,
+ users: ps.users,
+ caseSensitive: ps.caseSensitive,
+ withReplies: ps.withReplies,
+ withFile: ps.withFile,
+ notify: ps.notify,
+ });
+
+ return await Antennas.pack(antenna.id);
+});
diff --git a/src/server/api/endpoints/clips/create.ts b/src/server/api/endpoints/clips/create.ts
new file mode 100644
index 0000000000..a6761c5533
--- /dev/null
+++ b/src/server/api/endpoints/clips/create.ts
@@ -0,0 +1,29 @@
+import $ from 'cafy';
+import define from '../../define';
+import { genId } from '../../../../misc/gen-id';
+import { Clips } from '../../../../models';
+
+export const meta = {
+ tags: ['clips'],
+
+ requireCredential: true,
+
+ kind: 'write:account',
+
+ params: {
+ name: {
+ validator: $.str.range(1, 100)
+ }
+ },
+};
+
+export default define(meta, async (ps, user) => {
+ const clip = await Clips.save({
+ id: genId(),
+ createdAt: new Date(),
+ userId: user.id,
+ name: ps.name,
+ });
+
+ return await Clips.pack(clip);
+});
diff --git a/src/server/api/endpoints/clips/delete.ts b/src/server/api/endpoints/clips/delete.ts
new file mode 100644
index 0000000000..7e185e4652
--- /dev/null
+++ b/src/server/api/endpoints/clips/delete.ts
@@ -0,0 +1,40 @@
+import $ from 'cafy';
+import { ID } from '../../../../misc/cafy-id';
+import define from '../../define';
+import { ApiError } from '../../error';
+import { Clips } from '../../../../models';
+
+export const meta = {
+ tags: ['clips'],
+
+ requireCredential: true,
+
+ kind: 'write:account',
+
+ params: {
+ clipId: {
+ validator: $.type(ID),
+ }
+ },
+
+ errors: {
+ noSuchClip: {
+ message: 'No such clip.',
+ code: 'NO_SUCH_CLIP',
+ id: '70ca08ba-6865-4630-b6fb-8494759aa754'
+ }
+ }
+};
+
+export default define(meta, async (ps, user) => {
+ const clip = await Clips.findOne({
+ id: ps.clipId,
+ userId: user.id
+ });
+
+ if (clip == null) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
+
+ await Clips.delete(clip.id);
+});
diff --git a/src/server/api/endpoints/clips/list.ts b/src/server/api/endpoints/clips/list.ts
new file mode 100644
index 0000000000..aa16a18d42
--- /dev/null
+++ b/src/server/api/endpoints/clips/list.ts
@@ -0,0 +1,18 @@
+import define from '../../define';
+import { Clips } from '../../../../models';
+
+export const meta = {
+ tags: ['clips', 'account'],
+
+ requireCredential: true,
+
+ kind: 'read:account',
+};
+
+export default define(meta, async (ps, me) => {
+ const clips = await Clips.find({
+ userId: me.id,
+ });
+
+ return await Promise.all(clips.map(x => Clips.pack(x)));
+});
diff --git a/src/server/api/endpoints/clips/notes.ts b/src/server/api/endpoints/clips/notes.ts
new file mode 100644
index 0000000000..4e76a4d1f3
--- /dev/null
+++ b/src/server/api/endpoints/clips/notes.ts
@@ -0,0 +1,67 @@
+import $ from 'cafy';
+import { ID } from '../../../../misc/cafy-id';
+import define from '../../define';
+import { Clips, 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 = {
+ tags: ['account', 'notes', 'clips'],
+
+ requireCredential: true,
+
+ kind: 'read:account',
+
+ params: {
+ limit: {
+ validator: $.optional.num.range(1, 100),
+ default: 10
+ },
+
+ sinceId: {
+ validator: $.optional.type(ID),
+ },
+
+ untilId: {
+ validator: $.optional.type(ID),
+ },
+ },
+
+ errors: {
+ noSuchClip: {
+ message: 'No such list.',
+ code: 'NO_SUCH_CLIP',
+ id: '1d7645e6-2b6d-4635-b0fe-fe22b0e72e00'
+ }
+ }
+};
+
+export default define(meta, async (ps, user) => {
+ const clip = await Clips.findOne({
+ id: ps.clipId,
+ userId: user.id
+ });
+
+ if (clip == null) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
+
+ const clipQuery = ClipNotes.createQueryBuilder('joining')
+ .select('joining.noteId')
+ .where('joining.clipId = :clipId', { clipId: clip.id });
+
+ const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
+ .andWhere(`note.id IN (${ clipQuery.getQuery() })`)
+ .leftJoinAndSelect('note.user', 'user')
+ .setParameters(clipQuery.getParameters());
+
+ generateVisibilityQuery(query, user);
+ generateMuteQuery(query, user);
+
+ const notes = await query
+ .take(ps.limit!)
+ .getMany();
+
+ return await Notes.packMany(notes, user);
+});
diff --git a/src/server/api/endpoints/clips/show.ts b/src/server/api/endpoints/clips/show.ts
new file mode 100644
index 0000000000..0766b3e929
--- /dev/null
+++ b/src/server/api/endpoints/clips/show.ts
@@ -0,0 +1,41 @@
+import $ from 'cafy';
+import { ID } from '../../../../misc/cafy-id';
+import define from '../../define';
+import { ApiError } from '../../error';
+import { Clips } from '../../../../models';
+
+export const meta = {
+ tags: ['clips', 'account'],
+
+ requireCredential: true,
+
+ kind: 'read:account',
+
+ params: {
+ clipId: {
+ validator: $.type(ID),
+ },
+ },
+
+ errors: {
+ noSuchClip: {
+ message: 'No such clip.',
+ code: 'NO_SUCH_CLIP',
+ id: 'c3c5fe33-d62c-44d2-9ea5-d997703f5c20'
+ },
+ }
+};
+
+export default define(meta, async (ps, me) => {
+ // Fetch the clip
+ const clip = await Clips.findOne({
+ id: ps.clipId,
+ userId: me.id,
+ });
+
+ if (clip == null) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
+
+ return await Clips.pack(clip);
+});
diff --git a/src/server/api/endpoints/clips/update.ts b/src/server/api/endpoints/clips/update.ts
new file mode 100644
index 0000000000..d1c31eb8e6
--- /dev/null
+++ b/src/server/api/endpoints/clips/update.ts
@@ -0,0 +1,49 @@
+import $ from 'cafy';
+import { ID } from '../../../../misc/cafy-id';
+import define from '../../define';
+import { ApiError } from '../../error';
+import { Clips } from '../../../../models';
+
+export const meta = {
+ tags: ['clips'],
+
+ requireCredential: true,
+
+ kind: 'write:account',
+
+ params: {
+ clipId: {
+ validator: $.type(ID),
+ },
+
+ name: {
+ validator: $.str.range(1, 100),
+ }
+ },
+
+ errors: {
+ noSuchClip: {
+ message: 'No such clip.',
+ code: 'NO_SUCH_CLIP',
+ id: 'b4d92d70-b216-46fa-9a3f-a8c811699257'
+ },
+ }
+};
+
+export default define(meta, async (ps, user) => {
+ // Fetch the clip
+ const clip = await Clips.findOne({
+ id: ps.clipId,
+ userId: user.id
+ });
+
+ if (clip == null) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
+
+ await Clips.update(clip.id, {
+ name: ps.name
+ });
+
+ return await Clips.pack(clip.id);
+});
diff --git a/src/server/api/endpoints/federation/followers.ts b/src/server/api/endpoints/federation/followers.ts
new file mode 100644
index 0000000000..d885daf70e
--- /dev/null
+++ b/src/server/api/endpoints/federation/followers.ts
@@ -0,0 +1,51 @@
+import $ from 'cafy';
+import { ID } from '../../../../misc/cafy-id';
+import define from '../../define';
+import { Followings } from '../../../../models';
+import { makePaginationQuery } from '../../common/make-pagination-query';
+
+export const meta = {
+ tags: ['users'],
+
+ requireCredential: false,
+
+ params: {
+ host: {
+ validator: $.str
+ },
+
+ sinceId: {
+ validator: $.optional.type(ID),
+ },
+
+ untilId: {
+ validator: $.optional.type(ID),
+ },
+
+ limit: {
+ validator: $.optional.num.range(1, 100),
+ default: 10
+ },
+ },
+
+ res: {
+ type: 'array' as const,
+ optional: false as const, nullable: false as const,
+ items: {
+ type: 'object' as const,
+ optional: false as const, nullable: false as const,
+ ref: 'Following',
+ }
+ },
+};
+
+export default define(meta, async (ps, me) => {
+ const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId)
+ .andWhere(`following.followeeHost = :host`, { host: ps.host });
+
+ const followings = await query
+ .take(ps.limit!)
+ .getMany();
+
+ return await Followings.packMany(followings, me, { populateFollowee: true });
+});
diff --git a/src/server/api/endpoints/federation/following.ts b/src/server/api/endpoints/federation/following.ts
new file mode 100644
index 0000000000..1f79817318
--- /dev/null
+++ b/src/server/api/endpoints/federation/following.ts
@@ -0,0 +1,51 @@
+import $ from 'cafy';
+import { ID } from '../../../../misc/cafy-id';
+import define from '../../define';
+import { Followings } from '../../../../models';
+import { makePaginationQuery } from '../../common/make-pagination-query';
+
+export const meta = {
+ tags: ['users'],
+
+ requireCredential: false,
+
+ params: {
+ host: {
+ validator: $.str
+ },
+
+ sinceId: {
+ validator: $.optional.type(ID),
+ },
+
+ untilId: {
+ validator: $.optional.type(ID),
+ },
+
+ limit: {
+ validator: $.optional.num.range(1, 100),
+ default: 10
+ },
+ },
+
+ res: {
+ type: 'array' as const,
+ optional: false as const, nullable: false as const,
+ items: {
+ type: 'object' as const,
+ optional: false as const, nullable: false as const,
+ ref: 'Following',
+ }
+ },
+};
+
+export default define(meta, async (ps, me) => {
+ const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId)
+ .andWhere(`following.followerHost = :host`, { host: ps.host });
+
+ const followings = await query
+ .take(ps.limit!)
+ .getMany();
+
+ return await Followings.packMany(followings, me, { populateFollowee: true });
+});
diff --git a/src/server/api/endpoints/federation/instances.ts b/src/server/api/endpoints/federation/instances.ts
index bc0eb9a1d7..002cfd4335 100644
--- a/src/server/api/endpoints/federation/instances.ts
+++ b/src/server/api/endpoints/federation/instances.ts
@@ -9,6 +9,10 @@ export const meta = {
requireCredential: false,
params: {
+ host: {
+ validator: $.optional.nullable.str,
+ },
+
blocked: {
validator: $.optional.nullable.bool,
},
@@ -17,7 +21,19 @@ export const meta = {
validator: $.optional.nullable.bool,
},
- markedAsClosed: {
+ suspended: {
+ validator: $.optional.nullable.bool,
+ },
+
+ federating: {
+ validator: $.optional.nullable.bool,
+ },
+
+ subscribing: {
+ validator: $.optional.nullable.bool,
+ },
+
+ publishing: {
validator: $.optional.nullable.bool,
},
@@ -41,6 +57,8 @@ export default define(meta, async (ps, me) => {
const query = Instances.createQueryBuilder('instance');
switch (ps.sort) {
+ case '+pubSub': query.orderBy('instance.followingCount', 'DESC').orderBy('instance.followersCount', 'DESC'); break;
+ case '-pubSub': query.orderBy('instance.followingCount', 'ASC').orderBy('instance.followersCount', 'ASC'); break;
case '+notes': query.orderBy('instance.notesCount', 'DESC'); break;
case '-notes': query.orderBy('instance.notesCount', 'ASC'); break;
case '+users': query.orderBy('instance.usersCount', 'DESC'); break;
@@ -78,14 +96,42 @@ export default define(meta, async (ps, me) => {
}
}
- if (typeof ps.markedAsClosed === 'boolean') {
- if (ps.markedAsClosed) {
- query.andWhere('instance.isMarkedAsClosed = TRUE');
+ if (typeof ps.suspended === 'boolean') {
+ if (ps.suspended) {
+ query.andWhere('instance.isSuspended = TRUE');
} else {
- query.andWhere('instance.isMarkedAsClosed = FALSE');
+ query.andWhere('instance.isSuspended = FALSE');
}
}
+ if (typeof ps.federating === 'boolean') {
+ if (ps.federating) {
+ query.andWhere('((instance.followingCount > 0) OR (instance.followersCount > 0))');
+ } else {
+ query.andWhere('((instance.followingCount = 0) AND (instance.followersCount = 0))');
+ }
+ }
+
+ if (typeof ps.subscribing === 'boolean') {
+ if (ps.subscribing) {
+ query.andWhere('instance.followersCount > 0');
+ } else {
+ query.andWhere('instance.followersCount = 0');
+ }
+ }
+
+ if (typeof ps.publishing === 'boolean') {
+ if (ps.publishing) {
+ query.andWhere('instance.followingCount > 0');
+ } else {
+ query.andWhere('instance.followingCount = 0');
+ }
+ }
+
+ if (ps.host) {
+ query.andWhere('instance.host like :host', { host: '%' + ps.host.toLowerCase() + '%' })
+ }
+
const instances = await query.take(ps.limit!).skip(ps.offset).getMany();
return instances;
diff --git a/src/server/api/endpoints/federation/users.ts b/src/server/api/endpoints/federation/users.ts
new file mode 100644
index 0000000000..f69bbf949c
--- /dev/null
+++ b/src/server/api/endpoints/federation/users.ts
@@ -0,0 +1,51 @@
+import $ from 'cafy';
+import { ID } from '../../../../misc/cafy-id';
+import define from '../../define';
+import { Users } from '../../../../models';
+import { makePaginationQuery } from '../../common/make-pagination-query';
+
+export const meta = {
+ tags: ['users'],
+
+ requireCredential: false,
+
+ params: {
+ host: {
+ validator: $.str
+ },
+
+ sinceId: {
+ validator: $.optional.type(ID),
+ },
+
+ untilId: {
+ validator: $.optional.type(ID),
+ },
+
+ limit: {
+ validator: $.optional.num.range(1, 100),
+ default: 10
+ },
+ },
+
+ res: {
+ type: 'array' as const,
+ optional: false as const, nullable: false as const,
+ items: {
+ type: 'object' as const,
+ optional: false as const, nullable: false as const,
+ ref: 'User',
+ }
+ },
+};
+
+export default define(meta, async (ps, me) => {
+ const query = makePaginationQuery(Users.createQueryBuilder('user'), ps.sinceId, ps.untilId)
+ .andWhere(`user.host = :host`, { host: ps.host });
+
+ const users = await query
+ .take(ps.limit!)
+ .getMany();
+
+ return await Users.packMany(users, me, { detail: true });
+});
diff --git a/src/server/api/endpoints/i/notifications.ts b/src/server/api/endpoints/i/notifications.ts
index cd00501a2e..f624550d49 100644
--- a/src/server/api/endpoints/i/notifications.ts
+++ b/src/server/api/endpoints/i/notifications.ts
@@ -42,12 +42,12 @@ export const meta = {
},
includeTypes: {
- validator: $.optional.arr($.str.or(['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest'])),
+ validator: $.optional.arr($.str.or(['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted'])),
default: [] as string[]
},
excludeTypes: {
- validator: $.optional.arr($.str.or(['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest'])),
+ validator: $.optional.arr($.str.or(['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'receiveFollowRequest', 'followRequestAccepted'])),
default: [] as string[]
}
},
diff --git a/src/server/api/endpoints/i/read-announcement.ts b/src/server/api/endpoints/i/read-announcement.ts
new file mode 100644
index 0000000000..c5fbe7d576
--- /dev/null
+++ b/src/server/api/endpoints/i/read-announcement.ts
@@ -0,0 +1,60 @@
+import $ from 'cafy';
+import { ID } from '../../../../misc/cafy-id';
+import define from '../../define';
+import { ApiError } from '../../error';
+import { genId } from '../../../../misc/gen-id';
+import { AnnouncementReads, Announcements, Users } from '../../../../models';
+import { publishMainStream } from '../../../../services/stream';
+
+export const meta = {
+ tags: ['account'],
+
+ requireCredential: true,
+
+ kind: 'write:account',
+
+ params: {
+ announcementId: {
+ validator: $.type(ID),
+ },
+ },
+
+ errors: {
+ noSuchAnnouncement: {
+ message: 'No such announcement.',
+ code: 'NO_SUCH_ANNOUNCEMENT',
+ id: '184663db-df88-4bc2-8b52-fb85f0681939'
+ },
+ }
+};
+
+export default define(meta, async (ps, user) => {
+ // Check if announcement exists
+ const announcement = await Announcements.findOne(ps.announcementId);
+
+ if (announcement == null) {
+ throw new ApiError(meta.errors.noSuchAnnouncement);
+ }
+
+ // Check if already read
+ const read = await AnnouncementReads.findOne({
+ announcementId: ps.announcementId,
+ userId: user.id
+ });
+
+ if (read != null) {
+ return;
+ }
+
+ // Create read
+ await AnnouncementReads.save({
+ id: genId(),
+ createdAt: new Date(),
+ announcementId: ps.announcementId,
+ userId: user.id,
+ });
+
+ if (!await Users.getHasUnreadAnnouncement(user.id)) {
+ publishMainStream(user.id, 'readAllAnnouncements');
+ }
+});
diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts
index b71c35946e..2c605a6f0b 100644
--- a/src/server/api/endpoints/meta.ts
+++ b/src/server/api/endpoints/meta.ts
@@ -1,11 +1,8 @@
import $ from 'cafy';
-import * as os from 'os';
import config from '../../../config';
import define from '../define';
import { fetchMeta } from '../../../misc/fetch-meta';
-import { Emojis } from '../../../models';
-import { getConnection } from 'typeorm';
-import redis from '../../../db/redis';
+import { Emojis, Users } from '../../../models';
import { DB_MAX_NOTE_TEXT_LENGTH } from '../../../misc/hard-limits';
export const meta = {
@@ -83,11 +80,6 @@ export const meta = {
optional: false as const, nullable: false as const,
description: 'Whether disabled GTL.',
},
- enableEmojiReaction: {
- type: 'boolean' as const,
- optional: false as const, nullable: false as const,
- description: 'Whether enabled emoji reaction.',
- },
}
}
};
@@ -119,27 +111,15 @@ export default define(meta, async (ps, me) => {
uri: config.url,
description: instance.description,
langs: instance.langs,
- ToSUrl: instance.ToSUrl,
+ tosUrl: instance.ToSUrl,
repositoryUrl: instance.repositoryUrl,
feedbackUrl: instance.feedbackUrl,
secure: config.https != null,
- machine: os.hostname(),
- os: os.platform(),
- node: process.version,
- psql: await getConnection().query('SHOW server_version').then(x => x[0].server_version),
- redis: redis.server_info.redis_version,
-
- cpu: {
- model: os.cpus()[0].model,
- cores: os.cpus().length
- },
- announcements: instance.announcements || [],
disableRegistration: instance.disableRegistration,
disableLocalTimeline: instance.disableLocalTimeline,
disableGlobalTimeline: instance.disableGlobalTimeline,
- enableEmojiReaction: instance.enableEmojiReaction,
driveCapacityPerLocalUserMb: instance.localDriveCapacityMb,
driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb,
cacheRemoteFiles: instance.cacheRemoteFiles,
@@ -159,6 +139,7 @@ export default define(meta, async (ps, me) => {
category: e.category,
url: e.url,
})),
+ requireSetup: (await Users.count({})) === 0,
enableEmail: instance.enableEmail,
enableTwitterIntegration: instance.enableTwitterIntegration,
@@ -183,7 +164,7 @@ export default define(meta, async (ps, me) => {
};
}
- if (me && (me.isAdmin || me.isModerator)) {
+ if (me && me.isAdmin) {
response.useStarForReactionFallback = instance.useStarForReactionFallback;
response.pinnedUsers = instance.pinnedUsers;
response.hiddenTags = instance.hiddenTags;
diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts
index 810ad51b67..73db73ed97 100644
--- a/src/server/api/endpoints/notes/create.ts
+++ b/src/server/api/endpoints/notes/create.ts
@@ -113,23 +113,6 @@ export const meta = {
}
},
- geo: {
- validator: $.optional.nullable.obj({
- coordinates: $.arr().length(2)
- .item(0, $.num.range(-180, 180))
- .item(1, $.num.range(-90, 90)),
- altitude: $.nullable.num,
- accuracy: $.nullable.num,
- altitudeAccuracy: $.nullable.num,
- heading: $.nullable.num.range(0, 360),
- speed: $.nullable.num
- }).strict(),
- desc: {
- 'ja-JP': '位置情報'
- },
- ref: 'geo'
- },
-
fileIds: {
validator: $.optional.arr($.type(ID)).unique().range(1, 4),
desc: {
@@ -308,7 +291,6 @@ export default define(meta, async (ps, user, app) => {
apMentions: ps.noExtractMentions ? [] : undefined,
apHashtags: ps.noExtractHashtags ? [] : undefined,
apEmojis: ps.noExtractEmojis ? [] : undefined,
- geo: ps.geo
});
return {
diff --git a/src/server/api/endpoints/notes/featured.ts b/src/server/api/endpoints/notes/featured.ts
index 0a1d8668b0..a499afabf0 100644
--- a/src/server/api/endpoints/notes/featured.ts
+++ b/src/server/api/endpoints/notes/featured.ts
@@ -15,12 +15,17 @@ export const meta = {
params: {
limit: {
- validator: $.optional.num.range(1, 30),
+ validator: $.optional.num.range(1, 100),
default: 10,
desc: {
'ja-JP': '最大数'
}
- }
+ },
+
+ offset: {
+ validator: $.optional.num.min(0),
+ default: 0
+ },
},
res: {
@@ -35,6 +40,7 @@ export const meta = {
};
export default define(meta, async (ps, user) => {
+ const max = 30;
const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで
const query = Notes.createQueryBuilder('note')
@@ -46,7 +52,14 @@ export default define(meta, async (ps, user) => {
if (user) generateMuteQuery(query, user);
- const notes = await query.orderBy('note.score', 'DESC').take(ps.limit!).getMany();
+ let notes = await query
+ .orderBy('note.score', 'DESC')
+ .take(max)
+ .getMany();
+
+ notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
+
+ notes = notes.slice(ps.offset, ps.offset + ps.limit);
return await Notes.packMany(notes, user);
});
diff --git a/src/server/api/endpoints/notes/search.ts b/src/server/api/endpoints/notes/search.ts
index 5557b469e4..efc08d0d4a 100644
--- a/src/server/api/endpoints/notes/search.ts
+++ b/src/server/api/endpoints/notes/search.ts
@@ -1,11 +1,13 @@
import $ from 'cafy';
import es from '../../../../db/elasticsearch';
import define from '../../define';
-import { ApiError } from '../../error';
import { Notes } from '../../../../models';
import { In } from 'typeorm';
import { ID } from '../../../../misc/cafy-id';
import config from '../../../../config';
+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: {
@@ -22,16 +24,19 @@ export const meta = {
validator: $.str
},
+ sinceId: {
+ validator: $.optional.type(ID),
+ },
+
+ untilId: {
+ validator: $.optional.type(ID),
+ },
+
limit: {
validator: $.optional.num.range(1, 100),
default: 10
},
- offset: {
- validator: $.optional.num.min(0),
- default: 0
- },
-
host: {
validator: $.optional.nullable.str,
default: undefined
@@ -54,74 +59,80 @@ export const meta = {
},
errors: {
- searchingNotAvailable: {
- message: 'Searching not available.',
- code: 'SEARCHING_NOT_AVAILABLE',
- id: '7ee9c119-16a1-479f-a6fd-6fab00ed946f'
- }
}
};
export default define(meta, async (ps, me) => {
- if (es == null) throw new ApiError(meta.errors.searchingNotAvailable);
+ if (es == null) {
+ const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
+ .andWhere('note.text ILIKE :q', { q: `%${ps.query}%` })
+ .leftJoinAndSelect('note.user', 'user');
- const userQuery = ps.userId != null ? [{
- term: {
- userId: ps.userId
- }
- }] : [];
+ generateVisibilityQuery(query, me);
+ if (me) generateMuteQuery(query, me);
- const hostQuery = ps.userId == null ?
- ps.host === null ? [{
- bool: {
- must_not: {
- exists: {
- field: 'userHost'
- }
- }
- }
- }] : ps.host !== undefined ? [{
+ const notes = await query.take(ps.limit!).getMany();
+
+ return await Notes.packMany(notes, me);
+ } else {
+ const userQuery = ps.userId != null ? [{
term: {
- userHost: ps.host
+ userId: ps.userId
}
- }] : []
- : [];
+ }] : [];
- const result = await es.search({
- index: config.elasticsearch.index || 'misskey_note',
- body: {
- size: ps.limit!,
- from: ps.offset,
- query: {
+ const hostQuery = ps.userId == null ?
+ ps.host === null ? [{
bool: {
- must: [{
- simple_query_string: {
- fields: ['text'],
- query: ps.query.toLowerCase(),
- default_operator: 'and'
- },
- }, ...hostQuery, ...userQuery]
+ must_not: {
+ exists: {
+ field: 'userHost'
+ }
+ }
}
- },
- sort: [{
- _doc: 'desc'
- }]
- }
- });
+ }] : ps.host !== undefined ? [{
+ term: {
+ userHost: ps.host
+ }
+ }] : []
+ : [];
+
+ const result = await es.search({
+ index: config.elasticsearch.index || 'misskey_note',
+ body: {
+ size: ps.limit!,
+ from: ps.offset,
+ query: {
+ bool: {
+ must: [{
+ simple_query_string: {
+ fields: ['text'],
+ query: ps.query.toLowerCase(),
+ default_operator: 'and'
+ },
+ }, ...hostQuery, ...userQuery]
+ }
+ },
+ sort: [{
+ _doc: 'desc'
+ }]
+ }
+ });
- const hits = result.body.hits.hits.map((hit: any) => hit._id);
+ const hits = result.body.hits.hits.map((hit: any) => hit._id);
- if (hits.length === 0) return [];
+ if (hits.length === 0) return [];
- // Fetch found notes
- const notes = await Notes.find({
- where: {
- id: In(hits)
- },
- order: {
- id: -1
- }
- });
+ // Fetch found notes
+ const notes = await Notes.find({
+ where: {
+ id: In(hits)
+ },
+ order: {
+ id: -1
+ }
+ });
- return await Notes.packMany(notes, me);
+ return await Notes.packMany(notes, me);
+ }
});
diff --git a/src/server/api/endpoints/users/search-by-username-and-host.ts b/src/server/api/endpoints/users/search-by-username-and-host.ts
new file mode 100644
index 0000000000..8544731dfd
--- /dev/null
+++ b/src/server/api/endpoints/users/search-by-username-and-host.ts
@@ -0,0 +1,101 @@
+import $ from 'cafy';
+import define from '../../define';
+import { Users } from '../../../../models';
+import { User } from '../../../../models/entities/user';
+
+export const meta = {
+ desc: {
+ 'ja-JP': 'ユーザーを検索します。'
+ },
+
+ tags: ['users'],
+
+ requireCredential: false,
+
+ params: {
+ username: {
+ validator: $.optional.nullable.str,
+ desc: {
+ 'ja-JP': 'クエリ'
+ }
+ },
+
+ host: {
+ validator: $.optional.nullable.str,
+ desc: {
+ 'ja-JP': 'クエリ'
+ }
+ },
+
+ offset: {
+ validator: $.optional.num.min(0),
+ default: 0,
+ desc: {
+ 'ja-JP': 'オフセット'
+ }
+ },
+
+ limit: {
+ validator: $.optional.num.range(1, 100),
+ default: 10,
+ desc: {
+ 'ja-JP': '取得する数'
+ }
+ },
+
+ detail: {
+ validator: $.optional.bool,
+ default: true,
+ desc: {
+ 'ja-JP': '詳細なユーザー情報を含めるか否か'
+ }
+ },
+ },
+
+ res: {
+ type: 'array' as const,
+ optional: false as const, nullable: false as const,
+ items: {
+ type: 'object' as const,
+ optional: false as const, nullable: false as const,
+ ref: 'User',
+ }
+ },
+};
+
+export default define(meta, async (ps, me) => {
+ if (ps.host) {
+ const q = Users.createQueryBuilder('user')
+ .where('user.isSuspended = FALSE')
+ .andWhere('user.host LIKE :host', { host: ps.host.toLowerCase() + '%' });
+
+ if (ps.username) {
+ q.andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' })
+ }
+
+ const users = await q.take(ps.limit!).skip(ps.offset).getMany();
+
+ return await Users.packMany(users, me, { detail: ps.detail });
+ } else {
+ let users = await Users.createQueryBuilder('user')
+ .where('user.host IS NULL')
+ .andWhere('user.isSuspended = FALSE')
+ .andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' })
+ .take(ps.limit!)
+ .skip(ps.offset)
+ .getMany();
+
+ if (users.length < ps.limit!) {
+ const otherUsers = await Users.createQueryBuilder('user')
+ .where('user.host IS NOT NULL')
+ .andWhere('user.isSuspended = FALSE')
+ .andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' })
+ .take(ps.limit! - users.length)
+ .getMany();
+
+ users = users.concat(otherUsers);
+ }
+
+ return await Users.packMany(users, me, { detail: ps.detail });
+ }
+});