Skip to content

Commit

Permalink
enhance(role): ロールの割り当て時メモを残せるように (#842)
Browse files Browse the repository at this point in the history
  • Loading branch information
u1-liquid authored Dec 25, 2024
1 parent 346c848 commit 6542ad4
Show file tree
Hide file tree
Showing 14 changed files with 72 additions and 7 deletions.
13 changes: 13 additions & 0 deletions packages/backend/migration/1735078824104-RoleAssignment-Memo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export class RoleAssignmentMemo1735078824104 {
name = 'RoleAssignmentMemo1735078824104'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "role_assignment" ADD "memo" character varying(256)`);
await queryRunner.query(`COMMENT ON COLUMN "role_assignment"."memo" IS 'memo for the role assignment'`);
}

async down(queryRunner) {
await queryRunner.query(`COMMENT ON COLUMN "role_assignment"."memo" IS 'memo for the role assignment'`);
await queryRunner.query(`ALTER TABLE "role_assignment" DROP COLUMN "memo"`);
}
}
8 changes: 6 additions & 2 deletions packages/backend/src/core/RoleService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
}

@bindThis
public async assign(userId: MiUser['id'], roleId: MiRole['id'], expiresAt: Date | null = null, moderator?: MiUser): Promise<void> {
public async assign(userId: MiUser['id'], roleId: MiRole['id'], memo: string | null = null, expiresAt: Date | null = null, moderator?: MiUser): Promise<void> {
const now = Date.now();

const role = await this.rolesRepository.findOneByOrFail({ id: roleId });
Expand All @@ -512,6 +512,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
expiresAt: expiresAt,
roleId: roleId,
userId: userId,
memo: memo,
}).then(x => this.roleAssignmentsRepository.findOneByOrFail(x.identifiers[0]));

this.globalEventService.publishInternalEvent('userRoleAssigned', created);
Expand All @@ -521,9 +522,10 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
roleId: roleId,
});
}
} else if (existing.expiresAt !== expiresAt) {
} else if (existing.expiresAt !== expiresAt || existing.memo !== memo) {
await this.roleAssignmentsRepository.update(existing.id, {
expiresAt: expiresAt,
memo: memo,
});
} else {
throw new IdentifiableError('67d8689c-25c6-435f-8ced-631e4b81fce1', 'User is already assigned to this role.');
Expand All @@ -542,6 +544,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
userUsername: user.username,
userHost: user.host,
expiresAt: expiresAt ? expiresAt.toISOString() : null,
memo: memo,
});
}
}
Expand Down Expand Up @@ -582,6 +585,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
userId: userId,
userUsername: user.username,
userHost: user.host,
memo: existing.memo,
});
}
}
Expand Down
7 changes: 7 additions & 0 deletions packages/backend/src/models/RoleAssignment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,11 @@ export class MiRoleAssignment {
nullable: true,
})
public expiresAt: Date | null;

@Column('varchar', {
comment: 'memo for the role assignment',
length: 256,
nullable: true,
})
public memo: string | null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export const paramDef = {
properties: {
roleId: { type: 'string', format: 'misskey:id' },
userId: { type: 'string', format: 'misskey:id' },
memo: { type: 'string' },
expiresAt: {
type: 'integer',
nullable: true,
Expand Down Expand Up @@ -90,7 +91,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
return;
}

await this.roleService.assign(user.id, role.id, ps.expiresAt ? new Date(ps.expiresAt) : null, me);
await this.roleService.assign(user.id, role.id, ps.memo, ps.expiresAt ? new Date(ps.expiresAt) : null, me);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const meta = {
id: { type: 'string', format: 'misskey:id' },
createdAt: { type: 'string', format: 'date-time' },
user: { ref: 'UserDetailed' },
memo: { type: 'string', nullable: true },
expiresAt: { type: 'string', format: 'date-time', nullable: true },
},
required: ['id', 'createdAt', 'user'],
Expand Down Expand Up @@ -93,6 +94,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
id: assign.id,
createdAt: this.idService.parse(assign.id).date.toISOString(),
user: await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }),
memo: assign.memo,
expiresAt: assign.expiresAt?.toISOString() ?? null,
})));
});
Expand Down
5 changes: 5 additions & 0 deletions packages/backend/src/server/api/endpoints/admin/show-user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ export const meta = {
type: 'string',
optional: false, nullable: false,
},
memo: {
type: 'string',
optional: false, nullable: true,
}
},
},
},
Expand Down Expand Up @@ -262,6 +266,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
createdAt: this.idService.parse(a.id).date.toISOString(),
expiresAt: a.expiresAt ? a.expiresAt.toISOString() : null,
roleId: a.roleId,
memo: a.memo,
})),
};
});
Expand Down
2 changes: 2 additions & 0 deletions packages/backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,15 @@ export type ModerationLogPayloads = {
roleId: string;
roleName: string;
expiresAt: string | null;
memo: string | null;
};
unassignRole: {
userId: string;
userUsername: string;
userHost: string | null;
roleId: string;
roleName: string;
memo: string | null;
};
createRole: {
roleId: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/test/unit/RoleService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ describe('RoleService', () => {
},
},
});
await roleService.assign(user.id, role.id, new Date(Date.now() + (1000 * 60 * 60 * 24)));
await roleService.assign(user.id, role.id, 'test', new Date(Date.now() + (1000 * 60 * 60 * 24)));
metaService.fetch.mockResolvedValue({
policies: {
canManageCustomEmojis: false,
Expand Down
10 changes: 9 additions & 1 deletion packages/frontend/src/pages/admin-user.vue
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</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).memo">Memo: {{ info.roleAssigns.find(a => a.roleId === role.id).memo }}</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>
Expand Down Expand Up @@ -502,8 +503,15 @@ async function assignRole() {
: period === 'oneMonth' ? Date.now() + (1000 * 60 * 60 * 24 * 30)
: null;

const { canceled: canceled3, result: memo } = await os.inputText({
title: i18n.ts.addMemo,
type: 'textarea',
placeholder: i18n.ts.memo,
});
if (canceled3) return;

await os.apiWithDialog('admin/roles/assign', {
roleId, userId: user.value.id, expiresAt,
roleId, userId: user.value.id, memo: memo ?? undefined, expiresAt,
}).then(refreshUser);
}

Expand Down
3 changes: 3 additions & 0 deletions packages/frontend/src/pages/admin/modlog.ModLog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<template v-else-if="log.type === 'assignRole'">
<div>{{ i18n.ts.user }}: {{ log.info.userId }}</div>
<div>{{ i18n.ts.role }}: {{ log.info.roleName }} [{{ log.info.roleId }}]</div>
<div>{{ i18n.ts.memo }}: {{ log.info.memo }}</div>
<div>{{ i18n.ts.expirationDate }}: {{ log.info.expiresAt ?? i18n.ts.indefinitely }}</div>
</template>
<template v-else-if="log.type === 'unassignRole'">
<div>{{ i18n.ts.user }}: {{ log.info.userId }}</div>
<div>{{ i18n.ts.role }}: {{ log.info.roleName }} [{{ log.info.roleId }}]</div>
<div>{{ i18n.ts.memo }}: {{ log.info.memo }}</div>
</template>
<template v-else-if="log.type === 'updateCustomEmoji'">
<div>{{ i18n.ts.emoji }}: {{ log.info.emojiId }}</div>
Expand Down
10 changes: 9 additions & 1 deletion packages/frontend/src/pages/admin/roles.role.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div v-if="expandedItems.includes(item.id)" :class="$style.userItemSub">
<div>Assigned: <MkTime :time="item.createdAt" mode="detail"/></div>
<div v-if="item.memo">Memo: {{ item.memo }}</div>
<div v-if="item.expiresAt">Period: {{ new Date(item.expiresAt).toLocaleString() }}</div>
<div v-else>Period: {{ i18n.ts.indefinitely }}</div>
</div>
Expand Down Expand Up @@ -142,7 +143,14 @@ async function assign() {
: period === 'oneMonth' ? Date.now() + (1000 * 60 * 60 * 24 * 30)
: null;

await os.apiWithDialog('admin/roles/assign', { roleId: role.id, userId: user.id, expiresAt });
const { canceled: canceled3, result: memo } = await os.inputText({
title: i18n.ts.addMemo,
type: 'textarea',
placeholder: i18n.ts.memo,
});
if (canceled3) return;

await os.apiWithDialog('admin/roles/assign', { roleId: role.id, userId: user.id, memo: memo ?? undefined, expiresAt });
//role.users.push(user);
}

Expand Down
9 changes: 8 additions & 1 deletion packages/frontend/src/scripts/get-user-menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,14 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter
: period === 'oneMonth' ? Date.now() + (1000 * 60 * 60 * 24 * 30)
: null;

os.apiWithDialog('admin/roles/assign', { roleId: r.id, userId: user.id, expiresAt });
const { canceled: canceled3, result: memo } = await os.inputText({
title: i18n.ts.addMemo,
type: 'textarea',
placeholder: i18n.ts.memo,
});
if (canceled3) return;

os.apiWithDialog('admin/roles/assign', { roleId: r.id, userId: user.id, memo: memo ?? undefined, expiresAt });
},
}));
},
Expand Down
3 changes: 3 additions & 0 deletions packages/misskey-js/src/autogen/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9830,6 +9830,7 @@ export type operations = {
createdAt: string;
expiresAt: string | null;
roleId: string;
memo: string | null;
})[];
};
};
Expand Down Expand Up @@ -10619,6 +10620,7 @@ export type operations = {
roleId: string;
/** Format: misskey:id */
userId: string;
memo?: string;
expiresAt?: number | null;
};
};
Expand Down Expand Up @@ -10796,6 +10798,7 @@ export type operations = {
/** Format: date-time */
createdAt: string;
user: components['schemas']['UserDetailed'];
memo: string | null;
/** Format: date-time */
expiresAt: string | null;
})[];
Expand Down
2 changes: 2 additions & 0 deletions packages/misskey-js/src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,15 @@ export type ModerationLogPayloads = {
roleId: string;
roleName: string;
expiresAt: string | null;
memo: string | null;
};
unassignRole: {
userId: string;
userUsername: string;
userHost: string | null;
roleId: string;
roleName: string;
memo: string | null;
};
createRole: {
roleId: string;
Expand Down

0 comments on commit 6542ad4

Please sign in to comment.