This commit is contained in:
tamaina 2023-05-27 16:42:44 +00:00
parent 35f90e94c9
commit 84835be483
11 changed files with 335 additions and 231 deletions

View file

@ -5,44 +5,20 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { ApiError } from '@/server/api/error.js'; import { ApiError } from '@/server/api/error.js';
export const meta = {
tags: ['admin', 'role'],
requireCredential: true,
requireAdmin: true,
errors: {
noSuchRole: {
message: 'No such role.',
code: 'NO_SUCH_ROLE',
id: 'de0d6ecd-8e0a-4253-88ff-74bc89ae3d45',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
roleId: { type: 'string', format: 'misskey:id' },
},
required: [
'roleId',
],
} as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
@Injectable() @Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { export default class extends Endpoint<'admin/roles/delete'> {
name = 'admin/roles/delete' as const;
constructor( constructor(
@Inject(DI.rolesRepository) @Inject(DI.rolesRepository)
private rolesRepository: RolesRepository, private rolesRepository: RolesRepository,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
) { ) {
super(meta, paramDef, async (ps) => { super(async (ps) => {
const role = await this.rolesRepository.findOneBy({ id: ps.roleId }); const role = await this.rolesRepository.findOneBy({ id: ps.roleId });
if (role == null) { if (role == null) {
throw new ApiError(meta.errors.noSuchRole); throw new ApiError(this.meta.errors.noSuchRole);
} }
await this.rolesRepository.delete({ await this.rolesRepository.delete({
id: ps.roleId, id: ps.roleId,

View file

@ -4,31 +4,17 @@ import type { RolesRepository } from '@/models/index.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { RoleEntityService } from '@/core/entities/RoleEntityService.js'; import { RoleEntityService } from '@/core/entities/RoleEntityService.js';
export const meta = {
tags: ['admin', 'role'],
requireCredential: true,
requireModerator: true,
} as const;
export const paramDef = {
type: 'object',
properties: {
},
required: [
],
} as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
@Injectable() @Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { export default class extends Endpoint<'admin/roles/list'> {
name = 'admin/roles/list' as const;
constructor( constructor(
@Inject(DI.rolesRepository) @Inject(DI.rolesRepository)
private rolesRepository: RolesRepository, private rolesRepository: RolesRepository,
private roleEntityService: RoleEntityService, private roleEntityService: RoleEntityService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(async (ps, me) => {
const roles = await this.rolesRepository.find({ const roles = await this.rolesRepository.find({
order: { lastUsedAt: 'DESC' }, order: { lastUsedAt: 'DESC' },
}); });

View file

@ -5,44 +5,20 @@ import { DI } from '@/di-symbols.js';
import { ApiError } from '@/server/api/error.js'; import { ApiError } from '@/server/api/error.js';
import { RoleEntityService } from '@/core/entities/RoleEntityService.js'; import { RoleEntityService } from '@/core/entities/RoleEntityService.js';
export const meta = {
tags: ['admin', 'role'],
requireCredential: true,
requireModerator: true,
errors: {
noSuchRole: {
message: 'No such role.',
code: 'NO_SUCH_ROLE',
id: '07dc7d34-c0d8-49b7-96c6-db3ce64ee0b3',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
roleId: { type: 'string', format: 'misskey:id' },
},
required: [
'roleId',
],
} as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
@Injectable() @Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { export default class extends Endpoint<'admin/roles/show'> {
name = 'admin/roles/show' as const;
constructor( constructor(
@Inject(DI.rolesRepository) @Inject(DI.rolesRepository)
private rolesRepository: RolesRepository, private rolesRepository: RolesRepository,
private roleEntityService: RoleEntityService, private roleEntityService: RoleEntityService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(async (ps, me) => {
const role = await this.rolesRepository.findOneBy({ id: ps.roleId }); const role = await this.rolesRepository.findOneBy({ id: ps.roleId });
if (role == null) { if (role == null) {
throw new ApiError(meta.errors.noSuchRole); throw new ApiError(this.meta.errors.noSuchRole);
} }
return await this.roleEntityService.pack(role, me); return await this.roleEntityService.pack(role, me);
}); });

View file

@ -5,54 +5,10 @@ import { DI } from '@/di-symbols.js';
import { ApiError } from '@/server/api/error.js'; import { ApiError } from '@/server/api/error.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
export const meta = {
tags: ['admin', 'role'],
requireCredential: true,
requireModerator: true,
errors: {
noSuchRole: {
message: 'No such role.',
code: 'NO_SUCH_ROLE',
id: '6e519036-a70d-4c76-b679-bc8fb18194e2',
},
noSuchUser: {
message: 'No such user.',
code: 'NO_SUCH_USER',
id: '2b730f78-1179-461b-88ad-d24c9af1a5ce',
},
notAssigned: {
message: 'Not assigned.',
code: 'NOT_ASSIGNED',
id: 'b9060ac7-5c94-4da4-9f55-2047c953df44',
},
accessDenied: {
message: 'Only administrators can edit members of the role.',
code: 'ACCESS_DENIED',
id: '24636eee-e8c1-493e-94b2-e16ad401e262',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
roleId: { type: 'string', format: 'misskey:id' },
userId: { type: 'string', format: 'misskey:id' },
},
required: [
'roleId',
'userId',
],
} as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
@Injectable() @Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { export default class extends Endpoint<'admin/roles/unassign'> {
name = 'admin/roles/unassign' as const;
constructor( constructor(
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@ -62,19 +18,19 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private roleService: RoleService, private roleService: RoleService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(async (ps, me) => {
const role = await this.rolesRepository.findOneBy({ id: ps.roleId }); const role = await this.rolesRepository.findOneBy({ id: ps.roleId });
if (role == null) { if (role == null) {
throw new ApiError(meta.errors.noSuchRole); throw new ApiError(this.meta.errors.noSuchRole);
} }
if (!role.canEditMembersByModerator && !(await this.roleService.isAdministrator(me))) { if (!role.canEditMembersByModerator && !(await this.roleService.isAdministrator(me))) {
throw new ApiError(meta.errors.accessDenied); throw new ApiError(this.meta.errors.accessDenied);
} }
const user = await this.usersRepository.findOneBy({ id: ps.userId }); const user = await this.usersRepository.findOneBy({ id: ps.userId });
if (user == null) { if (user == null) {
throw new ApiError(meta.errors.noSuchUser); throw new ApiError(this.meta.errors.noSuchUser);
} }
await this.roleService.unassign(user.id, role.id); await this.roleService.unassign(user.id, role.id);

View file

@ -3,33 +3,15 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
export const meta = {
tags: ['admin', 'role'],
requireCredential: true,
requireAdmin: true,
} as const;
export const paramDef = {
type: 'object',
properties: {
policies: {
type: 'object',
},
},
required: [
'policies',
],
} as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
@Injectable() @Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { export default class extends Endpoint<'admin/roles/update-default-policies'> {
name = 'admin/roles/update-default-policies' as const;
constructor( constructor(
private metaService: MetaService, private metaService: MetaService,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
) { ) {
super(meta, paramDef, async (ps) => { super(async (ps) => {
await this.metaService.update({ await this.metaService.update({
policies: ps.policies, policies: ps.policies,
}); });

View file

@ -5,73 +5,20 @@ import { GlobalEventService } from '@/core/GlobalEventService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { ApiError } from '@/server/api/error.js'; import { ApiError } from '@/server/api/error.js';
export const meta = {
tags: ['admin', 'role'],
requireCredential: true,
requireAdmin: true,
errors: {
noSuchRole: {
message: 'No such role.',
code: 'NO_SUCH_ROLE',
id: 'cd23ef55-09ad-428a-ac61-95a45e124b32',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
roleId: { type: 'string', format: 'misskey:id' },
name: { type: 'string' },
description: { type: 'string' },
color: { type: 'string', nullable: true },
iconUrl: { type: 'string', nullable: true },
target: { type: 'string', enum: ['manual', 'conditional'] },
condFormula: { type: 'object' },
isPublic: { type: 'boolean' },
isModerator: { type: 'boolean' },
isAdministrator: { type: 'boolean' },
isExplorable: { type: 'boolean' },
asBadge: { type: 'boolean' },
canEditMembersByModerator: { type: 'boolean' },
displayOrder: { type: 'number' },
policies: {
type: 'object',
},
},
required: [
'roleId',
'name',
'description',
'color',
'iconUrl',
'target',
'condFormula',
'isPublic',
'isModerator',
'isAdministrator',
'asBadge',
'canEditMembersByModerator',
'displayOrder',
'policies',
],
} as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
@Injectable() @Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { export default class extends Endpoint<'admin/roles/update'> {
name = 'admin/roles/update' as const;
constructor( constructor(
@Inject(DI.rolesRepository) @Inject(DI.rolesRepository)
private rolesRepository: RolesRepository, private rolesRepository: RolesRepository,
private globalEventService: GlobalEventService, private globalEventService: GlobalEventService,
) { ) {
super(meta, paramDef, async (ps) => { super(async (ps) => {
const role = await this.rolesRepository.findOneBy({ id: ps.roleId }); const role = await this.rolesRepository.findOneBy({ id: ps.roleId });
if (role == null) { if (role == null) {
throw new ApiError(meta.errors.noSuchRole); throw new ApiError(this.meta.errors.noSuchRole);
} }
const date = new Date(); const date = new Date();

View file

@ -6,36 +6,12 @@ import { QueryService } from '@/core/QueryService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ApiError } from '../../../error.js'; import { ApiError } from '../../../error.js';
import { Packed } from 'misskey-js';
export const meta = {
tags: ['admin', 'role', 'users'],
requireCredential: false,
requireAdmin: true,
errors: {
noSuchRole: {
message: 'No such role.',
code: 'NO_SUCH_ROLE',
id: '224eff5e-2488-4b18-b3e7-f50d94421648',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
roleId: { type: 'string', format: 'misskey:id' },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
},
required: ['roleId'],
} as const;
// eslint-disable-next-line import/no-default-export // eslint-disable-next-line import/no-default-export
@Injectable() @Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { export default class extends Endpoint<'admin/roles/users'> {
name = 'admin/roles/users' as const;
constructor( constructor(
@Inject(DI.rolesRepository) @Inject(DI.rolesRepository)
private rolesRepository: RolesRepository, private rolesRepository: RolesRepository,
@ -46,13 +22,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private queryService: QueryService, private queryService: QueryService,
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
) { ) {
super(meta, paramDef, async (ps, me) => { super(async (ps, me) => {
const role = await this.rolesRepository.findOneBy({ const role = await this.rolesRepository.findOneBy({
id: ps.roleId, id: ps.roleId,
}); });
if (role == null) { if (role == null) {
throw new ApiError(meta.errors.noSuchRole); throw new ApiError(this.meta.errors.noSuchRole);
} }
const query = this.queryService.makePaginationQuery(this.roleAssignmentsRepository.createQueryBuilder('assign'), ps.sinceId, ps.untilId) const query = this.queryService.makePaginationQuery(this.roleAssignmentsRepository.createQueryBuilder('assign'), ps.sinceId, ps.untilId)
@ -67,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
.take(ps.limit) .take(ps.limit)
.getMany(); .getMany();
return await Promise.all(assigns.map(async assign => ({ return await Promise.all(assigns.map(async (assign): Promise<Packed<'RoleAssign'>> => ({
id: assign.id, id: assign.id,
createdAt: assign.createdAt, createdAt: assign.createdAt,
user: await this.userEntityService.pack(assign.user!, me, { detail: true }), user: await this.userEntityService.pack(assign.user!, me, { detail: true }),

View file

@ -1149,6 +1149,235 @@ export const endpoints = {
}, },
}], }],
}, },
'admin/roles/delete': {
tags: ['admin', 'role'],
requireCredential: true,
requireAdmin: true,
errors: {
noSuchRole: {
message: 'No such role.',
code: 'NO_SUCH_ROLE',
id: 'de0d6ecd-8e0a-4253-88ff-74bc89ae3d45',
},
},
defines: [{
req: {
type: 'object',
properties: {
roleId: { type: 'string', format: 'misskey:id' },
},
required: [
'roleId',
],
},
res: undefined,
}],
},
'admin/roles/list': {
tags: ['admin', 'role'],
requireCredential: true,
requireModerator: true,
defines: [{
req: undefined,
res: {
type: 'array',
items: {
$ref: 'https://misskey-hub.net/api/schemas/Role',
},
},
}],
},
'admin/roles/show': {
tags: ['admin', 'role'],
requireCredential: true,
requireModerator: true,
errors: {
noSuchRole: {
message: 'No such role.',
code: 'NO_SUCH_ROLE',
id: '07dc7d34-c0d8-49b7-96c6-db3ce64ee0b3',
},
},
defines: [{
req: {
type: 'object',
properties: {
roleId: { type: 'string', format: 'misskey:id' },
},
required: [
'roleId',
],
},
res: {
$ref: 'https://misskey-hub.net/api/schemas/Role',
}
}],
},
'admin/roles/unassign': {
tags: ['admin', 'role'],
requireCredential: true,
requireModerator: true,
errors: {
noSuchRole: {
message: 'No such role.',
code: 'NO_SUCH_ROLE',
id: '6e519036-a70d-4c76-b679-bc8fb18194e2',
},
noSuchUser: {
message: 'No such user.',
code: 'NO_SUCH_USER',
id: '2b730f78-1179-461b-88ad-d24c9af1a5ce',
},
notAssigned: {
message: 'Not assigned.',
code: 'NOT_ASSIGNED',
id: 'b9060ac7-5c94-4da4-9f55-2047c953df44',
},
accessDenied: {
message: 'Only administrators can edit members of the role.',
code: 'ACCESS_DENIED',
id: '24636eee-e8c1-493e-94b2-e16ad401e262',
},
},
defines: [{
req: {
type: 'object',
properties: {
roleId: { type: 'string', format: 'misskey:id' },
userId: { type: 'string', format: 'misskey:id' },
},
required: [
'roleId',
'userId',
],
},
res: undefined,
}],
},
'admin/roles/update-default-policies': {
tags: ['admin', 'role'],
requireCredential: true,
requireAdmin: true,
defines: [{
req: {
type: 'object',
properties: {
policies: {
type: 'object',
},
},
required: [
'policies',
],
},
res: undefined,
}],
},
'admin/roles/update': {
tags: ['admin', 'role'],
requireCredential: true,
requireAdmin: true,
errors: {
noSuchRole: {
message: 'No such role.',
code: 'NO_SUCH_ROLE',
id: 'cd23ef55-09ad-428a-ac61-95a45e124b32',
},
},
defines: [{
req: {
type: 'object',
properties: {
roleId: { type: 'string', format: 'misskey:id' },
name: { type: 'string' },
description: { type: 'string' },
color: { type: ['string', 'null'] },
iconUrl: { type: ['string', 'null'] },
target: { type: 'string', enum: ['manual', 'conditional'] },
condFormula: { type: 'object' },
isPublic: { type: 'boolean' },
isModerator: { type: 'boolean' },
isAdministrator: { type: 'boolean' },
isExplorable: { type: 'boolean' },
asBadge: { type: 'boolean' },
canEditMembersByModerator: { type: 'boolean' },
displayOrder: { type: 'number' },
policies: {
type: 'object',
},
},
required: [
'roleId',
'name',
'description',
'color',
'iconUrl',
'target',
'condFormula',
'isPublic',
'isModerator',
'isAdministrator',
'asBadge',
'canEditMembersByModerator',
'displayOrder',
'policies',
],
},
res: undefined,
}],
},
'admin/roles/users': {
tags: ['admin', 'role', 'users'],
requireCredential: false,
requireAdmin: true,
errors: {
noSuchRole: {
message: 'No such role.',
code: 'NO_SUCH_ROLE',
id: '224eff5e-2488-4b18-b3e7-f50d94421648',
},
},
defines: [{
req: {
type: 'object',
properties: {
roleId: { type: 'string', format: 'misskey:id' },
sinceId: { type: 'string', format: 'misskey:id' },
untilId: { type: 'string', format: 'misskey:id' },
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
},
required: ['roleId'],
},
res: {
type: 'array',
items: {
$ref: 'https://misskey-hub.net/api/schemas/RoleAssign'
},
}
}],
},
} as const satisfies { [x: string]: IEndpointMeta; }; } as const satisfies { [x: string]: IEndpointMeta; };
/** /**

View file

@ -36,6 +36,7 @@ import { packedFlashSchema } from './schemas/flash.js';
import { packedAdSchema } from './schemas/ad.js'; import { packedAdSchema } from './schemas/ad.js';
import { packedAnnouncementSchema } from './schemas/announcement.js'; import { packedAnnouncementSchema } from './schemas/announcement.js';
import { packedRelaySchema } from './schemas/relay.js'; import { packedRelaySchema } from './schemas/relay.js';
import { packedRoleAssignSchema } from './schemas/role.js';
import { Error, ApiError } from './schemas/error.js'; import { Error, ApiError } from './schemas/error.js';
import type { JSONSchema7, JSONSchema7Definition, GetDef, GetRefs, GetKeys, UnionToArray } from 'schema-type'; import type { JSONSchema7, JSONSchema7Definition, GetDef, GetRefs, GetKeys, UnionToArray } from 'schema-type';
@ -78,6 +79,8 @@ export const refs = {
Announcement: packedAnnouncementSchema, Announcement: packedAnnouncementSchema,
Relay: packedRelaySchema, Relay: packedRelaySchema,
RoleAssign: packedRoleAssignSchema,
Error: Error, Error: Error,
ApiError: ApiError, ApiError: ApiError,
} as const satisfies { [x: string]: JSONSchema7Definition }; } as const satisfies { [x: string]: JSONSchema7Definition };

View file

@ -0,0 +1,69 @@
import type { JSONSchema7Definition } from 'schema-type';
export const packedRoleSchema = {
$id: 'https://misskey-hub.net/api/schemas/Role',
type: 'object',
properties: {
id: { $ref: 'https://misskey-hub.net/api/schemas/Id' },
createdAt: {
type: 'string',
format: 'date-time',
},
updatedAt: {
type: 'string',
format: 'date-time',
},
name: {
type: 'string',
},
description: {
type: 'string',
},
color: {
type: ['string', 'null'],
},
iconUrl: {
type: ['string', 'null'],
},
target: {
enum: [
'manual',
'conditional',
],
}
},
required: [
'id',
'createdAt',
'updatedAt',
],
}
export const packedRoleAssignSchema = {
$id: 'https://misskey-hub.net/api/schemas/RoleAssign',
type: 'object',
properties: {
id: { $ref: 'https://misskey-hub.net/api/schemas/Id' },
createdAt: {
type: 'string',
format: 'date-time',
},
user: { $ref: 'https://misskey-hub.net/api/schemas/UserDetailed' },
expiresAt: {
oneOf: [{
type: 'string',
format: 'date-time',
}, {
type: 'null',
}],
},
},
required: [
'id',
'createdAt',
'user',
'expiresAt',
],
} as const satisfies JSONSchema7Definition;

View file

@ -100,6 +100,10 @@ describe('schemas', () => {
test('relay', () => { test('relay', () => {
type Relay = Packed<'Relay'>; type Relay = Packed<'Relay'>;
}); });
test('role': () => {
type RoleAssign = Packed<'RoleAssign'>;
});
test('error', () => { test('error', () => {
type Error = Packed<'Error'>; type Error = Packed<'Error'>;
type ApiError = Packed<'ApiError'>; type ApiError = Packed<'ApiError'>;