View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1133 Approved-by: dakkar <dakkar@thenautilus.net> Approved-by: Hazelnoot <acomputerdog@gmail.com>
This commit is contained in:
commit
a4c0ef824c
11 changed files with 219 additions and 0 deletions
|
|
@ -737,6 +737,17 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
|||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async clone(role: MiRole, moderator?: MiUser): Promise<MiRole> {
|
||||
const suffix = ' (cloned)';
|
||||
const newName = role.name.slice(0, 256 - suffix.length) + suffix;
|
||||
|
||||
return this.create({
|
||||
...role,
|
||||
name: newName,
|
||||
}, moderator);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async delete(role: MiRole, moderator?: MiUser): Promise<void> {
|
||||
await this.rolesRepository.delete({ id: role.id });
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ export * as 'admin/reset-password' from './endpoints/admin/reset-password.js';
|
|||
export * as 'admin/resolve-abuse-user-report' from './endpoints/admin/resolve-abuse-user-report.js';
|
||||
export * as 'admin/roles/assign' from './endpoints/admin/roles/assign.js';
|
||||
export * as 'admin/roles/create' from './endpoints/admin/roles/create.js';
|
||||
export * as 'admin/roles/clone' from './endpoints/admin/roles/clone.js';
|
||||
export * as 'admin/roles/delete' from './endpoints/admin/roles/delete.js';
|
||||
export * as 'admin/roles/list' from './endpoints/admin/roles/list.js';
|
||||
export * as 'admin/roles/show' from './endpoints/admin/roles/show.js';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { RolesRepository } from '@/models/_.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { RoleEntityService } from '@/core/entities/RoleEntityService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin', 'role'],
|
||||
|
||||
requireCredential: true,
|
||||
requireAdmin: true,
|
||||
kind: 'write:admin:roles',
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'Role',
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRole: {
|
||||
message: 'No such role.',
|
||||
code: 'NO_SUCH_ROLE',
|
||||
id: '93cc897a-b5f9-431f-b9b7-ee59035a5aed',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
roleId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: [
|
||||
'roleId',
|
||||
],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.rolesRepository)
|
||||
private rolesRepository: RolesRepository,
|
||||
private roleEntityService: RoleEntityService,
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const role = await this.rolesRepository.findOneBy({ id: ps.roleId });
|
||||
if (role == null) {
|
||||
throw new ApiError(meta.errors.noSuchRole);
|
||||
}
|
||||
|
||||
const cloned = await this.roleService.clone(role, me);
|
||||
|
||||
return this.roleEntityService.pack(cloned, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -983,4 +983,50 @@ describe('RoleService', () => {
|
|||
expect(notificationService.createNotification).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('clone', () => {
|
||||
test('clones a role', async () => {
|
||||
const role = await createRole({
|
||||
name: 'original role',
|
||||
color: '#ff0000',
|
||||
policies: {
|
||||
canManageCustomEmojis: {
|
||||
useDefault: false,
|
||||
priority: 0,
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const clonedRole = await roleService.clone(role);
|
||||
|
||||
expect(clonedRole).toBeDefined();
|
||||
expect(clonedRole.id).not.toBe(role.id);
|
||||
expect(clonedRole.name).toBe(`${role.name} (cloned)`);
|
||||
|
||||
expect(clonedRole).toEqual(expect.objectContaining({
|
||||
color: role.color,
|
||||
policies: {
|
||||
canManageCustomEmojis: {
|
||||
useDefault: false,
|
||||
priority: 0,
|
||||
value: true,
|
||||
},
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
test('clones a role with a too long name', async () => {
|
||||
const role = await createRole({
|
||||
name: 'a'.repeat(254),
|
||||
});
|
||||
|
||||
const clonedRole = await roleService.clone(role);
|
||||
|
||||
expect(clonedRole).toBeDefined();
|
||||
expect(clonedRole.id).not.toBe(role.id);
|
||||
expect(clonedRole.name.endsWith(' (cloned)')).toBeTruthy();
|
||||
expect(clonedRole.name.length).toBe(256);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<div class="_gaps">
|
||||
<div class="_buttons">
|
||||
<MkButton primary rounded @click="edit"><i class="ti ti-pencil"></i> {{ i18n.ts.edit }}</MkButton>
|
||||
<MkButton secondary rounded @click="clone"><i class="ti ti-copy"></i> {{ i18n.ts.clone }}</MkButton>
|
||||
<MkButton danger rounded @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||
</div>
|
||||
<MkFolder>
|
||||
|
|
@ -97,6 +98,13 @@ function edit() {
|
|||
router.push('/admin/roles/' + role.id + '/edit');
|
||||
}
|
||||
|
||||
async function clone() {
|
||||
const newRole = await misskeyApi('admin/roles/clone', {
|
||||
roleId: role.id,
|
||||
});
|
||||
router.push('/admin/roles/' + newRole.id + '/edit');
|
||||
}
|
||||
|
||||
async function del() {
|
||||
const { canceled } = await os.confirm({
|
||||
type: 'warning',
|
||||
|
|
|
|||
|
|
@ -326,6 +326,12 @@ type AdminResolveAbuseUserReportRequest = operations['admin___resolve-abuse-user
|
|||
// @public (undocumented)
|
||||
type AdminRolesAssignRequest = operations['admin___roles___assign']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type AdminRolesCloneRequest = operations['admin___roles___clone']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type AdminRolesCloneResponse = operations['admin___roles___clone']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type AdminRolesCreateRequest = operations['admin___roles___create']['requestBody']['content']['application/json'];
|
||||
|
||||
|
|
@ -1585,6 +1591,8 @@ declare namespace entities {
|
|||
AdminResetPasswordResponse,
|
||||
AdminResolveAbuseUserReportRequest,
|
||||
AdminRolesAssignRequest,
|
||||
AdminRolesCloneRequest,
|
||||
AdminRolesCloneResponse,
|
||||
AdminRolesCreateRequest,
|
||||
AdminRolesCreateResponse,
|
||||
AdminRolesDeleteRequest,
|
||||
|
|
|
|||
|
|
@ -856,6 +856,17 @@ declare module '../api.js' {
|
|||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:admin:roles*
|
||||
*/
|
||||
request<E extends 'admin/roles/clone', P extends Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
params: P,
|
||||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -98,6 +98,8 @@ import type {
|
|||
AdminResetPasswordResponse,
|
||||
AdminResolveAbuseUserReportRequest,
|
||||
AdminRolesAssignRequest,
|
||||
AdminRolesCloneRequest,
|
||||
AdminRolesCloneResponse,
|
||||
AdminRolesCreateRequest,
|
||||
AdminRolesCreateResponse,
|
||||
AdminRolesDeleteRequest,
|
||||
|
|
@ -738,6 +740,7 @@ export type Endpoints = {
|
|||
'admin/reset-password': { req: AdminResetPasswordRequest; res: AdminResetPasswordResponse };
|
||||
'admin/resolve-abuse-user-report': { req: AdminResolveAbuseUserReportRequest; res: EmptyResponse };
|
||||
'admin/roles/assign': { req: AdminRolesAssignRequest; res: EmptyResponse };
|
||||
'admin/roles/clone': { req: AdminRolesCloneRequest; res: AdminRolesCloneResponse };
|
||||
'admin/roles/create': { req: AdminRolesCreateRequest; res: AdminRolesCreateResponse };
|
||||
'admin/roles/delete': { req: AdminRolesDeleteRequest; res: EmptyResponse };
|
||||
'admin/roles/list': { req: EmptyRequest; res: AdminRolesListResponse };
|
||||
|
|
|
|||
|
|
@ -101,6 +101,8 @@ export type AdminResetPasswordRequest = operations['admin___reset-password']['re
|
|||
export type AdminResetPasswordResponse = operations['admin___reset-password']['responses']['200']['content']['application/json'];
|
||||
export type AdminResolveAbuseUserReportRequest = operations['admin___resolve-abuse-user-report']['requestBody']['content']['application/json'];
|
||||
export type AdminRolesAssignRequest = operations['admin___roles___assign']['requestBody']['content']['application/json'];
|
||||
export type AdminRolesCloneRequest = operations['admin___roles___clone']['requestBody']['content']['application/json'];
|
||||
export type AdminRolesCloneResponse = operations['admin___roles___clone']['responses']['200']['content']['application/json'];
|
||||
export type AdminRolesCreateRequest = operations['admin___roles___create']['requestBody']['content']['application/json'];
|
||||
export type AdminRolesCreateResponse = operations['admin___roles___create']['responses']['200']['content']['application/json'];
|
||||
export type AdminRolesDeleteRequest = operations['admin___roles___delete']['requestBody']['content']['application/json'];
|
||||
|
|
|
|||
|
|
@ -711,6 +711,15 @@ export type paths = {
|
|||
*/
|
||||
post: operations['admin___roles___assign'];
|
||||
};
|
||||
'/admin/roles/clone': {
|
||||
/**
|
||||
* admin/roles/clone
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:admin:roles*
|
||||
*/
|
||||
post: operations['admin___roles___clone'];
|
||||
};
|
||||
'/admin/roles/create': {
|
||||
/**
|
||||
* admin/roles/create
|
||||
|
|
@ -10385,6 +10394,60 @@ export type operations = {
|
|||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* admin/roles/clone
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:admin:roles*
|
||||
*/
|
||||
admin___roles___clone: {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
roleId: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description OK (with results) */
|
||||
200: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Role'];
|
||||
};
|
||||
};
|
||||
/** @description Client error */
|
||||
400: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Authentication error */
|
||||
401: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Forbidden error */
|
||||
403: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description I'm Ai */
|
||||
418: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Internal server error */
|
||||
500: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* admin/roles/create
|
||||
* @description No description provided.
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ collapseRenotes: "Collapse boosts you've already seen"
|
|||
collapseRenotesDescription: "Collapse boosts that you have boosted or reacted to"
|
||||
collapseNotesRepliedTo: "Collapse notes replied to"
|
||||
collapseFiles: "Collapse files"
|
||||
clone: "Clone"
|
||||
uncollapseCW: "Uncollapse CWs on notes"
|
||||
expandLongNote: "Always expand long notes"
|
||||
autoloadConversation: "Load conversation on replies"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue