From 8e4ad4b9195f168d66d3ce09a12afe736ceb481c Mon Sep 17 00:00:00 2001 From: Satsuki Yanagi <17376330+u1-liquid@users.noreply.github.com> Date: Wed, 17 Jul 2019 18:59:10 +0900 Subject: Improve usability of users view (#5176) * Improve usability of users view Resolve #5173 * Fix query * Follow review and fix * Follow review --- src/server/api/endpoints/admin/show-users.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src/server/api/endpoints') diff --git a/src/server/api/endpoints/admin/show-users.ts b/src/server/api/endpoints/admin/show-users.ts index 8733d87a38..89e0cf1e2a 100644 --- a/src/server/api/endpoints/admin/show-users.ts +++ b/src/server/api/endpoints/admin/show-users.ts @@ -49,6 +49,16 @@ export const meta = { 'remote', ]), default: 'local' + }, + + username: { + validator: $.optional.str, + default: null + }, + + hostname: { + validator: $.optional.str, + default: null } } }; @@ -70,6 +80,14 @@ export default define(meta, async (ps, me) => { case 'remote': query.andWhere('user.host IS NOT NULL'); break; } + if (ps.username) { + query.andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' }); + } + + if (ps.hostname) { + query.andWhere('user.host like :hostname', { hostname: '%' + ps.hostname.toLowerCase() + '%' }); + } + switch (ps.sort) { case '+follower': query.orderBy('user.followersCount', 'DESC'); break; case '-follower': query.orderBy('user.followersCount', 'ASC'); break; -- cgit v1.2.3-freya From ef44eda69eefbdeeb1efee1c8351be081938cae5 Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Thu, 18 Jul 2019 00:11:39 +0900 Subject: Mastodonのリンクの所有者認証に対応 (#5161) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Profile metadata を設定できるように * API desc --- locales/ja-JP.yml | 3 ++ .../common/views/components/settings/profile.vue | 46 ++++++++++++++++++++++ src/models/repositories/user.ts | 1 + src/remote/activitypub/renderer/person.ts | 15 ++++++- src/server/api/endpoints/i/update.ts | 15 +++++++ src/server/web/index.ts | 10 ++++- src/server/web/views/base.pug | 1 + src/server/web/views/user.pug | 5 +++ 8 files changed, 92 insertions(+), 4 deletions(-) (limited to 'src/server/api/endpoints') diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 76fc26381f..b6bbb7e963 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -804,6 +804,9 @@ common/views/components/profile-editor.vue: danger-zone: "危険な設定" delete-account: "アカウントを削除" account-deleted: "アカウントが削除されました。データが消えるまで時間がかかる場合があります。" + profile-metadata: "プロフィール補足情報" + metadata-label: "ラベル" + metadata-content: "内容" common/views/components/user-list-editor.vue: users: "ユーザー" diff --git a/src/client/app/common/views/components/settings/profile.vue b/src/client/app/common/views/components/settings/profile.vue index 52ec8ceda3..edfc5a9edf 100644 --- a/src/client/app/common/views/components/settings/profile.vue +++ b/src/client/app/common/views/components/settings/profile.vue @@ -51,6 +51,26 @@ +
+
{{ $t('profile-metadata') }}
+ + {{ $t('metadata-label') }} + {{ $t('metadata-content') }} + + + {{ $t('metadata-label') }} + {{ $t('metadata-content') }} + + + {{ $t('metadata-label') }} + {{ $t('metadata-content') }} + + + {{ $t('metadata-label') }} + {{ $t('metadata-content') }} + +
+ {{ $t('save') }} @@ -189,6 +209,17 @@ export default Vue.extend({ this.isLocked = this.$store.state.i.isLocked; this.carefulBot = this.$store.state.i.carefulBot; this.autoAcceptFollowed = this.$store.state.i.autoAcceptFollowed; + + if (this.$store.state.i.fields) { + this.fieldName0 = this.$store.state.i.fields[0].name; + this.fieldValue0 = this.$store.state.i.fields[0].value; + this.fieldName1 = this.$store.state.i.fields[1].name; + this.fieldValue1 = this.$store.state.i.fields[1].value; + this.fieldName2 = this.$store.state.i.fields[2].name; + this.fieldValue2 = this.$store.state.i.fields[2].value; + this.fieldName3 = this.$store.state.i.fields[3].name; + this.fieldValue3 = this.$store.state.i.fields[3].value; + } }, methods: { @@ -237,6 +268,13 @@ export default Vue.extend({ }, save(notify) { + const fields = [ + { name: this.fieldName0, value: this.fieldValue0 }, + { name: this.fieldName1, value: this.fieldValue1 }, + { name: this.fieldName2, value: this.fieldValue2 }, + { name: this.fieldName3, value: this.fieldValue3 }, + ]; + this.saving = true; this.$root.api('i/update', { @@ -247,6 +285,7 @@ export default Vue.extend({ birthday: this.birthday || null, avatarId: this.avatarId || undefined, bannerId: this.bannerId || undefined, + fields, isCat: !!this.isCat, isBot: !!this.isBot, isLocked: !!this.isLocked, @@ -389,4 +428,11 @@ export default Vue.extend({ height 72px margin auto +.fields + > header + padding 8px 0px + font-weight bold + > div + padding-left 16px + diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index 4e85fd7b93..a04b87f77c 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -148,6 +148,7 @@ export class UserRepository extends Repository { description: profile!.description, location: profile!.location, birthday: profile!.birthday, + fields: profile!.fields, followersCount: user.followersCount, followingCount: user.followingCount, notesCount: user.notesCount, diff --git a/src/remote/activitypub/renderer/person.ts b/src/remote/activitypub/renderer/person.ts index efe52cdefb..d4c018fb78 100644 --- a/src/remote/activitypub/renderer/person.ts +++ b/src/remote/activitypub/renderer/person.ts @@ -21,13 +21,24 @@ export async function renderPerson(user: ILocalUser) { ]); const attachment: { - type: string, + type: 'PropertyValue', name: string, value: string, - verified_at?: string, identifier?: IIdentifier }[] = []; + if (profile.fields) { + for (const field of profile.fields) { + attachment.push({ + type: 'PropertyValue', + name: field.name, + value: (field.value != null && field.value.match(/^https?:/)) + ? `${new URL(field.value).href}` + : field.value + }); + } + } + if (profile.twitter) { attachment.push({ type: 'PropertyValue', diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index a454cdb940..149081e50b 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -77,6 +77,13 @@ export const meta = { } }, + fields: { + validator: $.optional.arr($.object()).range(1, 4), + desc: { + 'ja-JP': 'プロフィール補足情報' + } + }, + isLocked: { validator: $.optional.bool, desc: { @@ -226,6 +233,14 @@ export default define(meta, async (ps, user, app) => { profileUpdates.pinnedPageId = null; } + if (ps.fields) { + profileUpdates.fields = ps.fields + .filter(x => typeof x.name === 'string' && x.name !== '' && typeof x.value === 'string' && x.value !== '') + .map(x => { + return { name: x.name, value: x.value }; + }); + } + //#region emojis/tags let emojis = [] as string[]; diff --git a/src/server/web/index.ts b/src/server/web/index.ts index 8cf6a75208..6c41bbde46 100644 --- a/src/server/web/index.ts +++ b/src/server/web/index.ts @@ -156,11 +156,17 @@ router.get('/@:user', async (ctx, next) => { if (user != null) { const profile = await UserProfiles.findOne(user.id).then(ensure); const meta = await fetchMeta(); + const me = profile.fields + ? profile.fields + .filter(filed => filed.value != null && filed.value.match(/^https?:/)) + .map(field => field.value) + : []; + await ctx.render('user', { - user, profile, + user, profile, me, instanceName: meta.name || 'Misskey' }); - ctx.set('Cache-Control', 'public, max-age=180'); + ctx.set('Cache-Control', 'public, max-age=30'); } else { // リモートユーザーなので await next(); diff --git a/src/server/web/views/base.pug b/src/server/web/views/base.pug index 733a306d56..16bea853e7 100644 --- a/src/server/web/views/base.pug +++ b/src/server/web/views/base.pug @@ -44,3 +44,4 @@ html + block content diff --git a/src/server/web/views/user.pug b/src/server/web/views/user.pug index 9b257afb7b..6ff86b09be 100644 --- a/src/server/web/views/user.pug +++ b/src/server/web/views/user.pug @@ -36,3 +36,8 @@ block meta link(rel='alternate' href=user.uri type='application/activity+json') if profile.url link(rel='alternate' href=profile.url type='text/html') + +block content + div#me + each m in me + a(rel='me' href=`${m}`) #{m} -- cgit v1.2.3-freya From 9c4e64b7b54886f97a9770b9d4904be29ba68eec Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Thu, 18 Jul 2019 02:03:28 +0900 Subject: Send Delete activity on suspend (#5165) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Send Delete Person activity * Delete activityの後にフォロー解除する * アカウント削除でもDelete activity --- src/server/api/endpoints/admin/suspend-user.ts | 6 +++- src/server/api/endpoints/admin/unsuspend-user.ts | 3 ++ src/server/api/endpoints/i/delete-account.ts | 4 +++ src/services/suspend-user.ts | 34 +++++++++++++++++++++++ src/services/unsuspend-user.ts | 35 ++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/services/suspend-user.ts create mode 100644 src/services/unsuspend-user.ts (limited to 'src/server/api/endpoints') diff --git a/src/server/api/endpoints/admin/suspend-user.ts b/src/server/api/endpoints/admin/suspend-user.ts index 09fdbb070e..6ba0d91505 100644 --- a/src/server/api/endpoints/admin/suspend-user.ts +++ b/src/server/api/endpoints/admin/suspend-user.ts @@ -5,6 +5,7 @@ import deleteFollowing from '../../../../services/following/delete'; import { Users, Followings } from '../../../../models'; import { User } from '../../../../models/entities/user'; import { insertModerationLog } from '../../../../services/insert-moderation-log'; +import { doPostSuspend } from '../../../../services/suspend-user'; export const meta = { desc: { @@ -51,7 +52,10 @@ export default define(meta, async (ps, me) => { targetId: user.id, }); - unFollowAll(user); + (async () => { + await doPostSuspend(user).catch(e => {}); + await unFollowAll(user).catch(e => {}); + })(); }); async function unFollowAll(follower: User) { diff --git a/src/server/api/endpoints/admin/unsuspend-user.ts b/src/server/api/endpoints/admin/unsuspend-user.ts index a1c80d3121..237585e276 100644 --- a/src/server/api/endpoints/admin/unsuspend-user.ts +++ b/src/server/api/endpoints/admin/unsuspend-user.ts @@ -3,6 +3,7 @@ import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import { Users } from '../../../../models'; import { insertModerationLog } from '../../../../services/insert-moderation-log'; +import { doPostUnsuspend } from '../../../../services/unsuspend-user'; export const meta = { desc: { @@ -40,4 +41,6 @@ export default define(meta, async (ps, me) => { insertModerationLog(me, 'unsuspend', { targetId: user.id, }); + + doPostUnsuspend(user); }); diff --git a/src/server/api/endpoints/i/delete-account.ts b/src/server/api/endpoints/i/delete-account.ts index 8ec85c9f41..b4950cb1fb 100644 --- a/src/server/api/endpoints/i/delete-account.ts +++ b/src/server/api/endpoints/i/delete-account.ts @@ -3,6 +3,7 @@ import * as bcrypt from 'bcryptjs'; import define from '../../define'; import { Users, UserProfiles } from '../../../../models'; import { ensure } from '../../../../prelude/ensure'; +import { doPostSuspend } from '../../../../services/suspend-user'; export const meta = { requireCredential: true, @@ -26,5 +27,8 @@ export default define(meta, async (ps, user) => { throw new Error('incorrect password'); } + // 物理削除する前にDelete activityを送信する + await doPostSuspend(user).catch(e => {}); + await Users.delete(user.id); }); diff --git a/src/services/suspend-user.ts b/src/services/suspend-user.ts new file mode 100644 index 0000000000..a85188acbe --- /dev/null +++ b/src/services/suspend-user.ts @@ -0,0 +1,34 @@ +import renderDelete from '../remote/activitypub/renderer/delete'; +import { renderActivity } from '../remote/activitypub/renderer'; +import { deliver } from '../queue'; +import config from '../config'; +import { User } from '../models/entities/user'; +import { Users, Followings } from '../models'; +import { Not, IsNull } from 'typeorm'; + +export async function doPostSuspend(user: User) { + if (Users.isLocalUser(user)) { + // 知り得る全SharedInboxにDelete配信 + const content = renderActivity(renderDelete(`${config.url}/users/${user.id}`, user)); + + const queue: string[] = []; + + const followings = await Followings.find({ + where: [ + { followerSharedInbox: Not(IsNull()) }, + { followeeSharedInbox: Not(IsNull()) } + ], + select: ['followerSharedInbox', 'followeeSharedInbox'] + }); + + const inboxes = followings.map(x => x.followerSharedInbox || x.followeeSharedInbox); + + for (const inbox of inboxes) { + if (inbox != null && !queue.includes(inbox)) queue.push(inbox); + } + + for (const inbox of queue) { + deliver(user as any, content, inbox); + } + } +} diff --git a/src/services/unsuspend-user.ts b/src/services/unsuspend-user.ts new file mode 100644 index 0000000000..6cab375821 --- /dev/null +++ b/src/services/unsuspend-user.ts @@ -0,0 +1,35 @@ +import renderDelete from '../remote/activitypub/renderer/delete'; +import renderUndo from '../remote/activitypub/renderer/undo'; +import { renderActivity } from '../remote/activitypub/renderer'; +import { deliver } from '../queue'; +import config from '../config'; +import { User } from '../models/entities/user'; +import { Users, Followings } from '../models'; +import { Not, IsNull } from 'typeorm'; + +export async function doPostUnsuspend(user: User) { + if (Users.isLocalUser(user)) { + // 知り得る全SharedInboxにUndo Delete配信 + const content = renderActivity(renderUndo(renderDelete(`${config.url}/users/${user.id}`, user), user)); + + const queue: string[] = []; + + const followings = await Followings.find({ + where: [ + { followerSharedInbox: Not(IsNull()) }, + { followeeSharedInbox: Not(IsNull()) } + ], + select: ['followerSharedInbox', 'followeeSharedInbox'] + }); + + const inboxes = followings.map(x => x.followerSharedInbox || x.followeeSharedInbox); + + for (const inbox of inboxes) { + if (inbox != null && !queue.includes(inbox)) queue.push(inbox); + } + + for (const inbox of queue) { + deliver(user as any, content, inbox); + } + } +} -- cgit v1.2.3-freya