diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2da31c0f2..cbd6e095b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,17 @@ npm i -g ts-node
npm run migrate
```
+11.22.0 (2019/06/18)
+--------------------
+### ✨Improvements
+* 管理画面でデータベースの各テーブルのレコード数やサイズを確認できるように
+* サーバー情報にPostgreSQLのバージョンを追加
+
+### 🐛Fixes
+* リモートファイルのダウンロードに失敗することがある問題を修正
+* アンケートの期間を日時指定で選択すると日時がUTCになってしまう問題を修正
+* MFMのパースを修正
+
11.21.0 (2019/06/16)
--------------------
### ✨Improvements
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 7053360a1..d9223b1c4 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1226,8 +1226,12 @@ admin/views/index.vue:
abuse: "スパム報告"
queue: "ジョブキュー"
logs: "ログ"
+ db: "データベース"
back-to-misskey: "Misskeyに戻る"
+admin/views/db.vue:
+ tables: "テーブル"
+
admin/views/dashboard.vue:
dashboard: "ダッシュボード"
accounts: "アカウント"
diff --git a/package.json b/package.json
index 22cb43cb3..e5946f04f 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo ",
- "version": "11.21.0",
+ "version": "11.22.0",
"codename": "daybreak",
"repository": {
"type": "git",
@@ -214,7 +214,7 @@
"style-loader": "0.23.1",
"stylus": "0.54.5",
"stylus-loader": "3.0.2",
- "summaly": "2.2.0",
+ "summaly": "2.3.0",
"systeminformation": "4.11.1",
"syuilo-password-strength": "0.0.1",
"terser-webpack-plugin": "1.3.0",
diff --git a/src/client/app/admin/views/dashboard.vue b/src/client/app/admin/views/dashboard.vue
index 639ec190e..15bd853c1 100644
--- a/src/client/app/admin/views/dashboard.vue
+++ b/src/client/app/admin/views/dashboard.vue
@@ -124,7 +124,7 @@ export default Vue.extend({
this.connection = this.$root.stream.useSharedConnection('serverStats');
this.updateStats();
- this.clock = setInterval(this.updateStats, 1000);
+ this.clock = setInterval(this.updateStats, 3000);
this.$root.getMeta().then(meta => {
this.meta = meta;
diff --git a/src/client/app/admin/views/db.vue b/src/client/app/admin/views/db.vue
new file mode 100644
index 000000000..7818546e7
--- /dev/null
+++ b/src/client/app/admin/views/db.vue
@@ -0,0 +1,39 @@
+
+
+
+ {{ $t('tables') }}
+
+ {{ table }} {{ tables[table].count | number }} {{ tables[table].size | bytes }}
+
+
+
+
+
+
diff --git a/src/client/app/admin/views/index.vue b/src/client/app/admin/views/index.vue
index 43e47038f..8a13fe1bf 100644
--- a/src/client/app/admin/views/index.vue
+++ b/src/client/app/admin/views/index.vue
@@ -22,6 +22,7 @@
{{ $t('instance') }}
{{ $t('queue') }}
{{ $t('logs') }}
+ {{ $t('db') }}
{{ $t('moderators') }}
{{ $t('users') }}
{{ $t('@.drive') }}
@@ -43,6 +44,7 @@
+
@@ -59,19 +61,20 @@
import Vue from 'vue';
import i18n from '../../i18n';
import { version } from '../../config';
-import XDashboard from "./dashboard.vue";
-import XInstance from "./instance.vue";
-import XQueue from "./queue.vue";
-import XLogs from "./logs.vue";
-import XModerators from "./moderators.vue";
-import XEmoji from "./emoji.vue";
-import XAnnouncements from "./announcements.vue";
-import XUsers from "./users.vue";
-import XDrive from "./drive.vue";
-import XAbuse from "./abuse.vue";
-import XFederation from "./federation.vue";
+import XDashboard from './dashboard.vue';
+import XInstance from './instance.vue';
+import XQueue from './queue.vue';
+import XLogs from './logs.vue';
+import XDb from './db.vue';
+import XModerators from './moderators.vue';
+import XEmoji from './emoji.vue';
+import XAnnouncements from './announcements.vue';
+import XUsers from './users.vue';
+import XDrive from './drive.vue';
+import XAbuse from './abuse.vue';
+import XFederation from './federation.vue';
-import { faHeadset, faArrowLeft, faGlobe, faExclamationCircle, faTasks, faStream } from '@fortawesome/free-solid-svg-icons';
+import { faHeadset, faArrowLeft, faGlobe, faExclamationCircle, faTasks, faStream, faDatabase } from '@fortawesome/free-solid-svg-icons';
import { faGrin } from '@fortawesome/free-regular-svg-icons';
// Detect the user agent
@@ -85,6 +88,7 @@ export default Vue.extend({
XInstance,
XQueue,
XLogs,
+ XDb,
XModerators,
XEmoji,
XAnnouncements,
@@ -108,7 +112,8 @@ export default Vue.extend({
faGlobe,
faExclamationCircle,
faTasks,
- faStream
+ faStream,
+ faDatabase,
};
},
methods: {
diff --git a/src/client/app/common/views/components/poll-editor.vue b/src/client/app/common/views/components/poll-editor.vue
index ed1d02aa2..f7a4d3af8 100644
--- a/src/client/app/common/views/components/poll-editor.vue
+++ b/src/client/app/common/views/components/poll-editor.vue
@@ -89,9 +89,7 @@ export default Vue.extend({
get() {
const at = () => {
- const [date] = moment(this.atDate).toISOString().split('T');
- const [hour, minute] = this.atTime.split(':');
- return moment(`${date}T${hour}:${minute}Z`).valueOf();
+ return moment(`${this.atDate} ${this.atTime}`).valueOf();
};
const after = () => {
diff --git a/src/client/app/common/views/widgets/server.info.vue b/src/client/app/common/views/widgets/server.info.vue
index a97b4ec49..41ccd23bf 100644
--- a/src/client/app/common/views/widgets/server.info.vue
+++ b/src/client/app/common/views/widgets/server.info.vue
@@ -3,6 +3,7 @@
Maintainer: {{ meta.maintainerName }}
Machine: {{ meta.machine }}
Node: {{ meta.node }}
+ PSQL: {{ meta.psql }}
Version: {{ meta.version }}
diff --git a/src/config/load.ts b/src/config/load.ts
index 26b25eab4..aeed54d74 100644
--- a/src/config/load.ts
+++ b/src/config/load.ts
@@ -3,7 +3,6 @@
*/
import * as fs from 'fs';
-import { URL } from 'url';
import * as yaml from 'js-yaml';
import { Source, Mixin } from './types';
import * as pkg from '../../package.json';
diff --git a/src/mfm/fromHtml.ts b/src/mfm/fromHtml.ts
index 5fc4a1641..60293b07f 100644
--- a/src/mfm/fromHtml.ts
+++ b/src/mfm/fromHtml.ts
@@ -1,5 +1,4 @@
import { parseFragment, DefaultTreeDocumentFragment } from 'parse5';
-import { URL } from 'url';
import { urlRegex } from './prelude';
export function fromHtml(html: string): string {
diff --git a/src/mfm/language.ts b/src/mfm/language.ts
index 003ae348a..4750ea338 100644
--- a/src/mfm/language.ts
+++ b/src/mfm/language.ts
@@ -98,13 +98,13 @@ export const mfmLanguage = P.createLanguage({
const text = input.substr(i);
const match = text.match(/^(\*|_)([a-zA-Z0-9]+?[\s\S]*?)\1/);
if (!match) return P.makeFailure(i, 'not a italic');
- if (input[i - 1] != null && input[i - 1].match(/[a-z0-9]/i)) return P.makeFailure(i, 'not a italic');
+ if (input[i - 1] != null && input[i - 1] != ' ' && input[i - 1] != '\n') return P.makeFailure(i, 'not a italic');
return P.makeSuccess(i + match[0].length, match[2]);
});
return P.alt(xml, underscore).map(x => createTree('italic', r.inline.atLeast(1).tryParse(x), {}));
},
- strike: r => P.regexp(/~~(.+?)~~/, 1).map(x => createTree('strike', r.inline.atLeast(1).tryParse(x), {})),
+ strike: r => P.regexp(/~~([^\n~]+?)~~/, 1).map(x => createTree('strike', r.inline.atLeast(1).tryParse(x), {})),
motion: r => {
const paren = P.regexp(/\(\(\(([\s\S]+?)\)\)\)/, 1);
const xml = P.regexp(/(.+?)<\/motion>/, 1);
@@ -164,8 +164,10 @@ export const mfmLanguage = P.createLanguage({
} else
url = match[0];
url = removeOrphanedBrackets(url);
- if (url.endsWith('.')) url = url.substr(0, url.lastIndexOf('.'));
- if (url.endsWith(',')) url = url.substr(0, url.lastIndexOf(','));
+ while (url.endsWith('.') || url.endsWith(',')) {
+ if (url.endsWith('.')) url = url.substr(0, url.lastIndexOf('.'));
+ if (url.endsWith(',')) url = url.substr(0, url.lastIndexOf(','));
+ }
return P.makeSuccess(i + url.length, url);
}).map(x => createLeaf('url', { url: x }));
},
diff --git a/src/misc/convert-host.ts b/src/misc/convert-host.ts
index a5fb15c66..ad52e1258 100644
--- a/src/misc/convert-host.ts
+++ b/src/misc/convert-host.ts
@@ -1,6 +1,5 @@
import config from '../config';
import { toASCII } from 'punycode';
-import { URL } from 'url';
export function getFullApAccount(username: string, host: string | null) {
return host ? `${username}@${toPuny(host)}` : `${username}@${toPuny(config.host)}`;
diff --git a/src/misc/donwload-url.ts b/src/misc/donwload-url.ts
index 167e01fdd..e2f10c0e4 100644
--- a/src/misc/donwload-url.ts
+++ b/src/misc/donwload-url.ts
@@ -25,10 +25,8 @@ export async function downloadUrl(url: string, path: string) {
rej(error);
});
- const requestUrl = new URL(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url;
-
const req = request({
- url: requestUrl,
+ url: new URL(url).href, // https://github.com/syuilo/misskey/issues/2637
proxy: config.proxy,
timeout: 10 * 1000,
headers: {
diff --git a/src/queue/processors/inbox.ts b/src/queue/processors/inbox.ts
index 21a51c962..e71181ee7 100644
--- a/src/queue/processors/inbox.ts
+++ b/src/queue/processors/inbox.ts
@@ -3,7 +3,6 @@ import * as httpSignature from 'http-signature';
import { IRemoteUser } from '../../models/entities/user';
import perform from '../../remote/activitypub/perform';
import { resolvePerson, updatePerson } from '../../remote/activitypub/models/person';
-import { URL } from 'url';
import { publishApLogStream } from '../../services/stream';
import Logger from '../../services/logger';
import { registerOrFetchInstanceDoc } from '../../services/register-or-fetch-instance-doc';
diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts
index dcd64e0cd..bfcad100f 100644
--- a/src/remote/activitypub/models/person.ts
+++ b/src/remote/activitypub/models/person.ts
@@ -6,7 +6,6 @@ import { resolveImage } from './image';
import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type';
import { DriveFile } from '../../../models/entities/drive-file';
import { fromHtml } from '../../../mfm/fromHtml';
-import { URL } from 'url';
import { resolveNote, extractEmojis } from './note';
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
import { ITag, extractHashtags } from './tag';
diff --git a/src/remote/activitypub/request.ts b/src/remote/activitypub/request.ts
index bde4921c3..6d18e5328 100644
--- a/src/remote/activitypub/request.ts
+++ b/src/remote/activitypub/request.ts
@@ -1,6 +1,5 @@
import { request } from 'https';
import { sign } from 'http-signature';
-import { URL } from 'url';
import * as crypto from 'crypto';
import { lookup, IRunOptions } from 'lookup-dns-cache';
import * as promiseAny from 'promise-any';
diff --git a/src/remote/resolve-user.ts b/src/remote/resolve-user.ts
index 0e2b88f05..5253d684d 100644
--- a/src/remote/resolve-user.ts
+++ b/src/remote/resolve-user.ts
@@ -1,7 +1,6 @@
import webFinger from './webfinger';
import config from '../config';
import { createPerson, updatePerson } from './activitypub/models/person';
-import { URL } from 'url';
import { remoteLogger } from './logger';
import chalk from 'chalk';
import { User, IRemoteUser } from '../models/entities/user';
diff --git a/src/remote/webfinger.ts b/src/remote/webfinger.ts
index 800673943..101a31aab 100644
--- a/src/remote/webfinger.ts
+++ b/src/remote/webfinger.ts
@@ -1,6 +1,5 @@
import config from '../config';
import * as request from 'request-promise-native';
-import { URL } from 'url';
import { query as urlQuery } from '../prelude/url';
type ILink = {
diff --git a/src/server/api/endpoints/admin/get-table-stats.ts b/src/server/api/endpoints/admin/get-table-stats.ts
new file mode 100644
index 000000000..1abea1849
--- /dev/null
+++ b/src/server/api/endpoints/admin/get-table-stats.ts
@@ -0,0 +1,37 @@
+import define from '../../define';
+import { getConnection } from 'typeorm';
+
+export const meta = {
+ requireCredential: false,
+
+ desc: {
+ 'en-US': 'Get table stats'
+ },
+
+ tags: ['meta'],
+
+ params: {
+ },
+};
+
+export default define(meta, async () => {
+ const sizes = await
+ getConnection().query(`
+ SELECT relname AS "table", reltuples as "count", pg_total_relation_size(C.oid) AS "size"
+ FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
+ WHERE nspname NOT IN ('pg_catalog', 'information_schema')
+ AND C.relkind <> 'i'
+ AND nspname !~ '^pg_toast';`)
+ .then(recs => {
+ const res = {} as Record;
+ for (const rec of recs) {
+ res[rec.table] = {
+ count: parseInt(rec.count, 10),
+ size: parseInt(rec.size, 10),
+ };
+ }
+ return res;
+ });
+
+ return sizes;
+});
diff --git a/src/server/api/endpoints/i/update-email.ts b/src/server/api/endpoints/i/update-email.ts
index 56284499d..ca95e612a 100644
--- a/src/server/api/endpoints/i/update-email.ts
+++ b/src/server/api/endpoints/i/update-email.ts
@@ -8,6 +8,7 @@ import * as bcrypt from 'bcryptjs';
import { Users, UserProfiles } from '../../../../models';
import { ensure } from '../../../../prelude/ensure';
import { sendEmail } from '../../../../services/send-email';
+import { ApiError } from '../../error';
export const meta = {
requireCredential: true,
@@ -27,6 +28,14 @@ export const meta = {
email: {
validator: $.optional.nullable.str
},
+ },
+
+ errors: {
+ incorrectPassword: {
+ message: 'Incorrect password.',
+ code: 'INCORRECT_PASSWORD',
+ id: 'e54c1d7e-e7d6-4103-86b6-0a95069b4ad3'
+ },
}
};
@@ -37,7 +46,7 @@ export default define(meta, async (ps, user) => {
const same = await bcrypt.compare(ps.password, profile.password!);
if (!same) {
- throw new Error('incorrect password');
+ throw new ApiError(meta.errors.incorrectPassword);
}
await UserProfiles.update({ userId: user.id }, {
diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts
index d18543f56..4da0c7476 100644
--- a/src/server/api/endpoints/meta.ts
+++ b/src/server/api/endpoints/meta.ts
@@ -6,6 +6,7 @@ import { fetchMeta } from '../../../misc/fetch-meta';
import * as pkg from '../../../../package.json';
import { Emojis } from '../../../models';
import { types, bool } from '../../../misc/schema';
+import { getConnection } from 'typeorm';
export const meta = {
stability: 'stable',
@@ -114,6 +115,7 @@ export default define(meta, async (ps, me) => {
machine: os.hostname(),
os: os.platform(),
node: process.version,
+ psql: await getConnection().query('SHOW server_version').then(x => x[0].server_version),
cpu: {
model: os.cpus()[0].model,
diff --git a/src/services/chart/core.ts b/src/services/chart/core.ts
index 6a69a21b7..6564ef781 100644
--- a/src/services/chart/core.ts
+++ b/src/services/chart/core.ts
@@ -163,6 +163,19 @@ export default abstract class Chart> {
},
...Chart.convertSchemaToFlatColumnDefinitions(schema)
},
+ indices: [{
+ columns: ['date']
+ }, {
+ columns: ['span']
+ }, {
+ columns: ['group']
+ }, {
+ columns: ['span', 'date']
+ }, {
+ columns: ['date', 'group']
+ }, {
+ columns: ['span', 'date', 'group']
+ }]
});
}
diff --git a/test/mfm.ts b/test/mfm.ts
index 89b414eba..be8b65264 100644
--- a/test/mfm.ts
+++ b/test/mfm.ts
@@ -804,6 +804,14 @@ describe('MFM', () => {
]);
});
+ it('ignore trailing periods', () => {
+ const tokens = parse('https://example.com...');
+ assert.deepStrictEqual(tokens, [
+ leaf('url', { url: 'https://example.com' }),
+ text('...')
+ ]);
+ });
+
it('with comma', () => {
const tokens = parse('https://example.com/foo?bar=a,b');
assert.deepStrictEqual(tokens, [
@@ -1116,6 +1124,14 @@ describe('MFM', () => {
], {}),
]);
});
+
+ // https://misskey.io/notes/7u1kv5dmia
+ it('ignore internal tilde', () => {
+ const tokens = parse('~~~~~');
+ assert.deepStrictEqual(tokens, [
+ text('~~~~~')
+ ]);
+ });
});
describe('italic', () => {
@@ -1173,6 +1189,24 @@ describe('MFM', () => {
text('foo_bar_baz'),
]);
});
+
+ it('require spaces', () => {
+ const tokens = parse('4日目_L38b a_b');
+ assert.deepStrictEqual(tokens, [
+ text('4日目_L38b a_b'),
+ ]);
+ });
+
+ it('newline sandwich', () => {
+ const tokens = parse('foo\n_bar_\nbaz');
+ assert.deepStrictEqual(tokens, [
+ text('foo\n'),
+ tree('italic', [
+ text('bar')
+ ], {}),
+ text('\nbaz'),
+ ]);
+ });
});
});