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>
Esse commit está contido em:
commit
a4c0ef824c
11 arquivos alterados com 219 adições e 0 exclusões
|
|
@ -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
|
@bindThis
|
||||||
public async delete(role: MiRole, moderator?: MiUser): Promise<void> {
|
public async delete(role: MiRole, moderator?: MiUser): Promise<void> {
|
||||||
await this.rolesRepository.delete({ id: role.id });
|
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/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/assign' from './endpoints/admin/roles/assign.js';
|
||||||
export * as 'admin/roles/create' from './endpoints/admin/roles/create.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/delete' from './endpoints/admin/roles/delete.js';
|
||||||
export * as 'admin/roles/list' from './endpoints/admin/roles/list.js';
|
export * as 'admin/roles/list' from './endpoints/admin/roles/list.js';
|
||||||
export * as 'admin/roles/show' from './endpoints/admin/roles/show.js';
|
export * as 'admin/roles/show' from './endpoints/admin/roles/show.js';
|
||||||
|
|
|
||||||
65
packages/backend/src/server/api/endpoints/admin/roles/clone.ts
Arquivo normal
65
packages/backend/src/server/api/endpoints/admin/roles/clone.ts
Arquivo normal
|
|
@ -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();
|
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="_gaps">
|
||||||
<div class="_buttons">
|
<div class="_buttons">
|
||||||
<MkButton primary rounded @click="edit"><i class="ti ti-pencil"></i> {{ i18n.ts.edit }}</MkButton>
|
<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>
|
<MkButton danger rounded @click="del"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
|
||||||
</div>
|
</div>
|
||||||
<MkFolder>
|
<MkFolder>
|
||||||
|
|
@ -97,6 +98,13 @@ function edit() {
|
||||||
router.push('/admin/roles/' + role.id + '/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() {
|
async function del() {
|
||||||
const { canceled } = await os.confirm({
|
const { canceled } = await os.confirm({
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
|
|
|
||||||
|
|
@ -326,6 +326,12 @@ type AdminResolveAbuseUserReportRequest = operations['admin___resolve-abuse-user
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
type AdminRolesAssignRequest = operations['admin___roles___assign']['requestBody']['content']['application/json'];
|
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)
|
// @public (undocumented)
|
||||||
type AdminRolesCreateRequest = operations['admin___roles___create']['requestBody']['content']['application/json'];
|
type AdminRolesCreateRequest = operations['admin___roles___create']['requestBody']['content']['application/json'];
|
||||||
|
|
||||||
|
|
@ -1585,6 +1591,8 @@ declare namespace entities {
|
||||||
AdminResetPasswordResponse,
|
AdminResetPasswordResponse,
|
||||||
AdminResolveAbuseUserReportRequest,
|
AdminResolveAbuseUserReportRequest,
|
||||||
AdminRolesAssignRequest,
|
AdminRolesAssignRequest,
|
||||||
|
AdminRolesCloneRequest,
|
||||||
|
AdminRolesCloneResponse,
|
||||||
AdminRolesCreateRequest,
|
AdminRolesCreateRequest,
|
||||||
AdminRolesCreateResponse,
|
AdminRolesCreateResponse,
|
||||||
AdminRolesDeleteRequest,
|
AdminRolesDeleteRequest,
|
||||||
|
|
|
||||||
|
|
@ -856,6 +856,17 @@ declare module '../api.js' {
|
||||||
credential?: string | null,
|
credential?: string | null,
|
||||||
): Promise<SwitchCaseResponseType<E, P>>;
|
): 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.
|
* No description provided.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,8 @@ import type {
|
||||||
AdminResetPasswordResponse,
|
AdminResetPasswordResponse,
|
||||||
AdminResolveAbuseUserReportRequest,
|
AdminResolveAbuseUserReportRequest,
|
||||||
AdminRolesAssignRequest,
|
AdminRolesAssignRequest,
|
||||||
|
AdminRolesCloneRequest,
|
||||||
|
AdminRolesCloneResponse,
|
||||||
AdminRolesCreateRequest,
|
AdminRolesCreateRequest,
|
||||||
AdminRolesCreateResponse,
|
AdminRolesCreateResponse,
|
||||||
AdminRolesDeleteRequest,
|
AdminRolesDeleteRequest,
|
||||||
|
|
@ -738,6 +740,7 @@ export type Endpoints = {
|
||||||
'admin/reset-password': { req: AdminResetPasswordRequest; res: AdminResetPasswordResponse };
|
'admin/reset-password': { req: AdminResetPasswordRequest; res: AdminResetPasswordResponse };
|
||||||
'admin/resolve-abuse-user-report': { req: AdminResolveAbuseUserReportRequest; res: EmptyResponse };
|
'admin/resolve-abuse-user-report': { req: AdminResolveAbuseUserReportRequest; res: EmptyResponse };
|
||||||
'admin/roles/assign': { req: AdminRolesAssignRequest; 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/create': { req: AdminRolesCreateRequest; res: AdminRolesCreateResponse };
|
||||||
'admin/roles/delete': { req: AdminRolesDeleteRequest; res: EmptyResponse };
|
'admin/roles/delete': { req: AdminRolesDeleteRequest; res: EmptyResponse };
|
||||||
'admin/roles/list': { req: EmptyRequest; res: AdminRolesListResponse };
|
'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 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 AdminResolveAbuseUserReportRequest = operations['admin___resolve-abuse-user-report']['requestBody']['content']['application/json'];
|
||||||
export type AdminRolesAssignRequest = operations['admin___roles___assign']['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 AdminRolesCreateRequest = operations['admin___roles___create']['requestBody']['content']['application/json'];
|
||||||
export type AdminRolesCreateResponse = operations['admin___roles___create']['responses']['200']['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'];
|
export type AdminRolesDeleteRequest = operations['admin___roles___delete']['requestBody']['content']['application/json'];
|
||||||
|
|
|
||||||
|
|
@ -711,6 +711,15 @@ export type paths = {
|
||||||
*/
|
*/
|
||||||
post: operations['admin___roles___assign'];
|
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': {
|
||||||
/**
|
/**
|
||||||
* 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
|
* admin/roles/create
|
||||||
* @description No description provided.
|
* @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"
|
collapseRenotesDescription: "Collapse boosts that you have boosted or reacted to"
|
||||||
collapseNotesRepliedTo: "Collapse notes replied to"
|
collapseNotesRepliedTo: "Collapse notes replied to"
|
||||||
collapseFiles: "Collapse files"
|
collapseFiles: "Collapse files"
|
||||||
|
clone: "Clone"
|
||||||
uncollapseCW: "Uncollapse CWs on notes"
|
uncollapseCW: "Uncollapse CWs on notes"
|
||||||
expandLongNote: "Always expand long notes"
|
expandLongNote: "Always expand long notes"
|
||||||
autoloadConversation: "Load conversation on replies"
|
autoloadConversation: "Load conversation on replies"
|
||||||
|
|
|
||||||
Carregando…
Adicionar tabela
Adicionar um link
Referência em uma nova issue