wip: ストリーム/api関連
This commit is contained in:
parent
b9c1f9a861
commit
80d5796d17
|
@ -118,8 +118,7 @@ export interface NoteEventTypes {
|
||||||
deletedAt: Date;
|
deletedAt: Date;
|
||||||
};
|
};
|
||||||
updated: {
|
updated: {
|
||||||
cw: string | null;
|
note: MiNote;
|
||||||
text: string;
|
|
||||||
};
|
};
|
||||||
reacted: {
|
reacted: {
|
||||||
reaction: string;
|
reaction: string;
|
||||||
|
|
|
@ -35,6 +35,7 @@ export type RolePolicies = {
|
||||||
gtlAvailable: boolean;
|
gtlAvailable: boolean;
|
||||||
ltlAvailable: boolean;
|
ltlAvailable: boolean;
|
||||||
canPublicNote: boolean;
|
canPublicNote: boolean;
|
||||||
|
canEditNote: boolean;
|
||||||
mentionLimit: number;
|
mentionLimit: number;
|
||||||
canInvite: boolean;
|
canInvite: boolean;
|
||||||
inviteLimit: number;
|
inviteLimit: number;
|
||||||
|
@ -63,6 +64,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
|
||||||
gtlAvailable: true,
|
gtlAvailable: true,
|
||||||
ltlAvailable: true,
|
ltlAvailable: true,
|
||||||
canPublicNote: true,
|
canPublicNote: true,
|
||||||
|
canEditNote: false,
|
||||||
mentionLimit: 20,
|
mentionLimit: 20,
|
||||||
canInvite: false,
|
canInvite: false,
|
||||||
inviteLimit: 0,
|
inviteLimit: 0,
|
||||||
|
@ -364,6 +366,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
|
||||||
gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)),
|
gtlAvailable: calc('gtlAvailable', vs => vs.some(v => v === true)),
|
||||||
ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
|
ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
|
||||||
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
|
canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
|
||||||
|
canEditNote: calc('canEditNote', vs => vs.some(v => v === true)),
|
||||||
mentionLimit: calc('mentionLimit', vs => Math.max(...vs)),
|
mentionLimit: calc('mentionLimit', vs => Math.max(...vs)),
|
||||||
canInvite: calc('canInvite', vs => vs.some(v => v === true)),
|
canInvite: calc('canInvite', vs => vs.some(v => v === true)),
|
||||||
inviteLimit: calc('inviteLimit', vs => Math.max(...vs)),
|
inviteLimit: calc('inviteLimit', vs => Math.max(...vs)),
|
||||||
|
|
|
@ -325,6 +325,7 @@ export class NoteEntityService implements OnModuleInit {
|
||||||
const packed: Packed<'Note'> = await awaitAll({
|
const packed: Packed<'Note'> = await awaitAll({
|
||||||
id: note.id,
|
id: note.id,
|
||||||
createdAt: this.idService.parse(note.id).date.toISOString(),
|
createdAt: this.idService.parse(note.id).date.toISOString(),
|
||||||
|
updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined,
|
||||||
userId: note.userId,
|
userId: note.userId,
|
||||||
user: packedUsers?.get(note.userId) ?? this.userEntityService.pack(note.user ?? note.userId, me),
|
user: packedUsers?.get(note.userId) ?? this.userEntityService.pack(note.user ?? note.userId, me),
|
||||||
text: text,
|
text: text,
|
||||||
|
|
|
@ -19,7 +19,7 @@ export class MiNote {
|
||||||
nullable: true,
|
nullable: true,
|
||||||
comment: 'The updated date of the Note.',
|
comment: 'The updated date of the Note.',
|
||||||
})
|
})
|
||||||
public updatedAt: Date;
|
public updatedAt: Date | null;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({
|
@Column({
|
||||||
|
|
|
@ -180,6 +180,10 @@ export const packedRolePoliciesSchema = {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
|
canEditNote: {
|
||||||
|
type: 'boolean',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
mentionLimit: {
|
mentionLimit: {
|
||||||
type: 'integer',
|
type: 'integer',
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
|
|
|
@ -0,0 +1,142 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ms from 'ms';
|
||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { In } from 'typeorm';
|
||||||
|
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||||
|
import type { DriveFilesRepository, UsersRepository } from '@/models/_.js';
|
||||||
|
import { NoteEditService } from '@/core/NoteEditService.js';
|
||||||
|
import { GetterService } from '@/server/api/GetterService.js';
|
||||||
|
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||||
|
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
|
||||||
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
tags: ['notes'],
|
||||||
|
|
||||||
|
requireCredential: true,
|
||||||
|
requireRolePolicy: 'canEditNote',
|
||||||
|
|
||||||
|
kind: 'write:notes',
|
||||||
|
|
||||||
|
limit: {
|
||||||
|
duration: ms('1hour'),
|
||||||
|
max: 10,
|
||||||
|
minInterval: ms('2min'),
|
||||||
|
},
|
||||||
|
|
||||||
|
errors: {
|
||||||
|
noSuchNote: {
|
||||||
|
message: 'No such note.',
|
||||||
|
code: 'NO_SUCH_NOTE',
|
||||||
|
id: 'a6584e14-6e01-4ad3-b566-851e7bf0d474',
|
||||||
|
},
|
||||||
|
accessDenied: {
|
||||||
|
message: 'Access denied.',
|
||||||
|
code: 'ACCESS_DENIED',
|
||||||
|
id: 'fe8d7103-0ea8-4ec3-814d-f8b401dc69e9',
|
||||||
|
},
|
||||||
|
containsProhibitedWords: {
|
||||||
|
message: 'Cannot post because it contains prohibited words.',
|
||||||
|
code: 'CONTAINS_PROHIBITED_WORDS',
|
||||||
|
id: 'aa6e01d3-a85c-669d-758a-76aab43af334',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const paramDef = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
noteId: { type: 'string', format: 'misskey:id' },
|
||||||
|
text: {
|
||||||
|
type: 'string',
|
||||||
|
minLength: 1,
|
||||||
|
maxLength: MAX_NOTE_TEXT_LENGTH,
|
||||||
|
nullable: false,
|
||||||
|
},
|
||||||
|
cw: {
|
||||||
|
type: 'string',
|
||||||
|
nullable: true,
|
||||||
|
maxLength: 100,
|
||||||
|
},
|
||||||
|
fileIds: {
|
||||||
|
type: 'array',
|
||||||
|
uniqueItems: true,
|
||||||
|
minItems: 1,
|
||||||
|
maxItems: 16,
|
||||||
|
items: { type: 'string', format: 'misskey:id' },
|
||||||
|
},
|
||||||
|
poll: {
|
||||||
|
type: 'object',
|
||||||
|
nullable: true,
|
||||||
|
properties: {
|
||||||
|
choices: {
|
||||||
|
type: 'array',
|
||||||
|
uniqueItems: true,
|
||||||
|
minItems: 2,
|
||||||
|
maxItems: 10,
|
||||||
|
items: { type: 'string', minLength: 1, maxLength: 50 },
|
||||||
|
},
|
||||||
|
multiple: { type: 'boolean' },
|
||||||
|
expiresAt: { type: 'integer', nullable: true },
|
||||||
|
expiredAfter: { type: 'integer', nullable: true, minimum: 1 },
|
||||||
|
},
|
||||||
|
required: ['choices'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['noteId', 'text', 'cw'],
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
|
constructor(
|
||||||
|
@Inject(DI.driveFilesRepository)
|
||||||
|
private driveFilesRepository: DriveFilesRepository,
|
||||||
|
|
||||||
|
@Inject(DI.usersRepository)
|
||||||
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
|
private getterService: GetterService,
|
||||||
|
private globalEventService: GlobalEventService,
|
||||||
|
private noteEditService: NoteEditService,
|
||||||
|
private roleService: RoleService,
|
||||||
|
) {
|
||||||
|
super(meta, paramDef, async (ps, me) => {
|
||||||
|
const note = await this.getterService.getNote(ps.noteId).catch(err => {
|
||||||
|
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
// この操作を行うのが投稿者とは限らない(例えばモデレーター)ため
|
||||||
|
if (!await this.roleService.isModerator(me) && (note.userId !== me.id) && (await this.roleService.getUserPolicies(me.id)).canEditNote !== true) {
|
||||||
|
throw new ApiError(meta.errors.accessDenied);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const targetNote = await this.noteEditService.edit(await this.usersRepository.findOneByOrFail({ id: note.userId }), note.id, {
|
||||||
|
text: ps.text,
|
||||||
|
cw: ps.cw,
|
||||||
|
files: ps.fileIds ? await this.driveFilesRepository.findBy({ id: In(ps.fileIds) }) : undefined,
|
||||||
|
poll: ps.poll ? {
|
||||||
|
choices: ps.poll.choices,
|
||||||
|
multiple: ps.poll.multiple ?? false,
|
||||||
|
expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
|
||||||
|
} : undefined,
|
||||||
|
}, undefined, me);
|
||||||
|
this.globalEventService.publishNoteStream(note.id, 'updated', {
|
||||||
|
note: targetNote,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof NoteEditService.ContainsProhibitedWordsError) {
|
||||||
|
throw new ApiError(meta.errors.containsProhibitedWords);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue