enhance: ユーザーにロールが期限付きでアサインされている場合、その期限をユーザーのモデレーションページで確認できるように

Close #11059
This commit is contained in:
syuilo 2023-07-20 10:54:41 +09:00
parent 2ddf575cdc
commit 871027fa0c
6 changed files with 51 additions and 11 deletions

View File

@ -15,12 +15,13 @@
## 13.x.x (unreleased) ## 13.x.x (unreleased)
### General ### General
- identicon生成を無効にしてパフォーマンスを向上させることができるようになりました
- サーバーのマシン情報の公開を無効にしてパフォーマンスを向上させることができるようになりました
- 招待機能を改善しました - 招待機能を改善しました
* 過去に発行した招待コードを確認できるようになりました * 過去に発行した招待コードを確認できるようになりました
* ロールごとに招待コードの発行数制限と制限対象期間、有効期限を設定できるようになりました * ロールごとに招待コードの発行数制限と制限対象期間、有効期限を設定できるようになりました
* 招待コードを作成したユーザーと使用したユーザーを確認できるようになりました * 招待コードを作成したユーザーと使用したユーザーを確認できるようになりました
- ユーザーにロールが期限付きでアサインされている場合、その期限をユーザーのモデレーションページで確認できるようになりました
- identicon生成を無効にしてパフォーマンスを向上させることができるようになりました
- サーバーのマシン情報の公開を無効にしてパフォーマンスを向上させることができるようになりました
### Client ### Client
- deck UIのカラムのメニューからアンテナとリストの編集画面を開けるように - deck UIのカラムのメニューからアンテナとリストの編集画面を開けるように

View File

@ -220,14 +220,19 @@ export class RoleService implements OnApplicationShutdown {
} }
@bindThis @bindThis
public async getUserRoles(userId: User['id']) { public async getUserAssigns(userId: User['id']) {
const now = Date.now(); const now = Date.now();
let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId })); let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId }));
// 期限切れのロールを除外 // 期限切れのロールを除外
assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now)); assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
const assignedRoleIds = assigns.map(x => x.roleId); return assigns;
}
@bindThis
public async getUserRoles(userId: User['id']) {
const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({})); const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
const assignedRoles = roles.filter(r => assignedRoleIds.includes(r.id)); const assigns = await this.getUserAssigns(userId);
const assignedRoles = roles.filter(r => assigns.map(x => x.roleId).includes(r.id));
const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null; const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, r.condFormula)); const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, r.condFormula));
return [...assignedRoles, ...matchedCondRoles]; return [...assignedRoles, ...matchedCondRoles];

View File

@ -61,6 +61,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const signins = await this.signinsRepository.findBy({ userId: user.id }); const signins = await this.signinsRepository.findBy({ userId: user.id });
const roleAssigns = await this.roleService.getUserAssigns(user.id);
const roles = await this.roleService.getUserRoles(user.id); const roles = await this.roleService.getUserRoles(user.id);
return { return {
@ -85,6 +86,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
signins, signins,
policies: await this.roleService.getUserPolicies(user.id), policies: await this.roleService.getUserPolicies(user.id),
roles: await this.roleEntityService.packMany(roles, me), roles: await this.roleEntityService.packMany(roles, me),
roleAssigns: roleAssigns.map(a => ({
createdAt: a.createdAt.toISOString(),
expiresAt: a.expiresAt ? a.expiresAt.toISOString() : null,
roleId: a.roleId,
})),
}; };
}); });
} }

View File

@ -40,7 +40,7 @@
</div> </div>
<div v-if="expandedItems.includes(item.id)" :class="$style.userItemSub"> <div v-if="expandedItems.includes(item.id)" :class="$style.userItemSub">
<div>Assigned: <MkTime :time="item.createdAt" mode="detail"/></div> <div>Assigned: <MkTime :time="item.createdAt" mode="detail"/></div>
<div v-if="item.expiresAt">Period: {{ item.expiresAt.toLocaleString() }}</div> <div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div>
<div v-else>Period: {{ i18n.ts.indefinitely }}</div> <div v-else>Period: {{ i18n.ts.indefinitely }}</div>
</div> </div>
</div> </div>

View File

@ -55,7 +55,7 @@
</div> </div>
<div v-if="expandedMuteItems.includes(item.id)" :class="$style.userItemSub"> <div v-if="expandedMuteItems.includes(item.id)" :class="$style.userItemSub">
<div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div> <div>Muted at: <MkTime :time="item.createdAt" mode="detail"/></div>
<div v-if="item.expiresAt">Period: {{ item.expiresAt.toLocaleString() }}</div> <div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div>
<div v-else>Period: {{ i18n.ts.indefinitely }}</div> <div v-else>Period: {{ i18n.ts.indefinitely }}</div>
</div> </div>
</div> </div>
@ -85,7 +85,7 @@
</div> </div>
<div v-if="expandedBlockItems.includes(item.id)" :class="$style.userItemSub"> <div v-if="expandedBlockItems.includes(item.id)" :class="$style.userItemSub">
<div>Blocked at: <MkTime :time="item.createdAt" mode="detail"/></div> <div>Blocked at: <MkTime :time="item.createdAt" mode="detail"/></div>
<div v-if="item.expiresAt">Period: {{ item.expiresAt.toLocaleString() }}</div> <div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div>
<div v-else>Period: {{ i18n.ts.indefinitely }}</div> <div v-else>Period: {{ i18n.ts.indefinitely }}</div>
</div> </div>
</div> </div>

View File

@ -112,10 +112,18 @@
<MkButton v-if="user.host == null && iAmModerator" primary rounded @click="assignRole"><i class="ti ti-plus"></i> {{ i18n.ts.assign }}</MkButton> <MkButton v-if="user.host == null && iAmModerator" primary rounded @click="assignRole"><i class="ti ti-plus"></i> {{ i18n.ts.assign }}</MkButton>
<div v-for="role in info.roles" :key="role.id" :class="$style.roleItem"> <div v-for="role in info.roles" :key="role.id" :class="$style.roleItem">
<div :class="$style.roleItemMain">
<MkRolePreview :class="$style.role" :role="role" :forModeration="true"/> <MkRolePreview :class="$style.role" :role="role" :forModeration="true"/>
<button class="_button" :class="$style.roleToggle" @click="toggleRoleItem(role)"><i class="ti ti-chevron-down"></i></button>
<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="unassignRole(role, $event)"><i class="ti ti-x"></i></button> <button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="unassignRole(role, $event)"><i class="ti ti-x"></i></button>
<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button> <button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button>
</div> </div>
<div v-if="expandedRoles.includes(role.id)" :class="$style.roleItemSub">
<div>Assigned: <MkTime :time="info.roleAssigns.find(a => a.roleId === role.id).createdAt" mode="detail"/></div>
<div v-if="info.roleAssigns.find(a => a.roleId === role.id).expiresAt">Period: {{ new Date(info.roleAssigns.find(a => a.roleId === role.id).expiresAt).toLocaleString() }}</div>
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
</div>
</div>
</div> </div>
</MkFolder> </MkFolder>
@ -220,6 +228,7 @@ const filesPagination = {
userId: props.userId, userId: props.userId,
})), })),
}; };
let expandedRoles = $ref([]);
function createFetcher() { function createFetcher() {
if (iAmModerator) { if (iAmModerator) {
@ -384,6 +393,14 @@ async function unassignRole(role, ev) {
}], ev.currentTarget ?? ev.target); }], ev.currentTarget ?? ev.target);
} }
function toggleRoleItem(role) {
if (expandedRoles.includes(role.id)) {
expandedRoles = expandedRoles.filter(x => x !== role.id);
} else {
expandedRoles.push(role.id);
}
}
watch(() => props.userId, () => { watch(() => props.userId, () => {
init = createFetcher(); init = createFetcher();
}, { }, {
@ -523,11 +540,22 @@ definePageMetadata(computed(() => ({
} }
.roleItem { .roleItem {
}
.roleItemMain {
display: flex; display: flex;
} }
.role { .role {
flex: 1; flex: 1;
min-width: 0;
margin-right: 8px;
}
.roleItemSub {
padding: 6px 12px;
font-size: 85%;
color: var(--fgTransparentWeak);
} }
.roleUnassign { .roleUnassign {