unify note create/edit logic to fix various logic bugs and validation gaps
This commit is contained in:
parent
24bc1c653d
commit
edb74066da
5 changed files with 93 additions and 144 deletions
|
|
@ -302,7 +302,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
case 'followers':
|
||||
// 他人のfollowers noteはreject
|
||||
if (data.renote.userId !== user.id) {
|
||||
throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is not public or home');
|
||||
throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Renote target is not public or home');
|
||||
}
|
||||
|
||||
// Renote対象がfollowersならfollowersにする
|
||||
|
|
@ -310,25 +310,53 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
break;
|
||||
case 'specified':
|
||||
// specified / direct noteはreject
|
||||
throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is not public or home');
|
||||
throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Renote target is not public or home');
|
||||
}
|
||||
|
||||
if (data.renote.userId !== user.id) {
|
||||
// Check local-only
|
||||
if (data.renote.localOnly && user.host != null) {
|
||||
throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Remote user cannot renote a local-only note');
|
||||
}
|
||||
|
||||
// Check visibility
|
||||
if (!await this.noteEntityService.isVisibleForMe(data.renote, user.id)) {
|
||||
throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Cannot renote an invisible note');
|
||||
}
|
||||
|
||||
// Check blocking
|
||||
if (await this.userBlockingService.checkBlocked(data.renote.userId, user.id)) {
|
||||
throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is blocked');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.reply) {
|
||||
if (isPureRenote(data.reply)) {
|
||||
throw new IdentifiableError('3ac74a84-8fd5-4bb0-870f-01804f82ce15', 'Cannot reply to a pure renote (boost)');
|
||||
}
|
||||
|
||||
if (data.reply.userId !== user.id) {
|
||||
// Check local-only
|
||||
if (data.reply.localOnly && user.host != null) {
|
||||
throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Remote user cannot reply to a local-only note');
|
||||
}
|
||||
|
||||
// Check visibility
|
||||
if (!await this.noteEntityService.isVisibleForMe(data.reply, user.id)) {
|
||||
throw new IdentifiableError('b98980fa-3780-406c-a935-b6d0eeee10d1', 'Cannot reply to an invisible note');
|
||||
}
|
||||
|
||||
// Check blocking
|
||||
if (await this.userBlockingService.checkBlocked(data.reply.userId, user.id)) {
|
||||
throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Reply target is blocked');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check quote permissions
|
||||
await this.checkQuotePermissions(data, user);
|
||||
|
||||
// Check blocking
|
||||
if (this.isRenote(data) && !this.isQuote(data)) {
|
||||
if (data.renote.userHost === null) {
|
||||
if (data.renote.userId !== user.id) {
|
||||
const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
|
||||
if (blocked) {
|
||||
throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is blocked');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 返信対象がpublicではないならhomeにする
|
||||
if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') {
|
||||
data.visibility = 'home';
|
||||
|
|
@ -486,14 +514,6 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
mandatoryCW: data.mandatoryCW,
|
||||
});
|
||||
|
||||
// should really not happen, but better safe than sorry
|
||||
if (data.reply?.id === insert.id) {
|
||||
throw new IdentifiableError('ea93b7c2-3d6c-4e10-946b-00d50b1a75cb', 'A note can\'t reply to itself');
|
||||
}
|
||||
if (data.renote?.id === insert.id) {
|
||||
throw new IdentifiableError('ea93b7c2-3d6c-4e10-946b-00d50b1a75cb', 'A note can\'t renote itself');
|
||||
}
|
||||
|
||||
if (data.uri != null) insert.uri = data.uri;
|
||||
if (data.url != null) insert.url = data.url;
|
||||
|
||||
|
|
|
|||
|
|
@ -308,18 +308,6 @@ export class NoteEditService implements OnApplicationShutdown {
|
|||
throw new IdentifiableError('fd4cc33e-2a37-48dd-99cc-9b806eb2031a', 'Cannot renote a pure renote (boost)');
|
||||
}
|
||||
|
||||
// Check for recursion
|
||||
let renoteId: string | null = data.renote.id;
|
||||
while (renoteId) {
|
||||
if (renoteId === oldnote.id) {
|
||||
throw new IdentifiableError('ea93b7c2-3d6c-4e10-946b-00d50b1a75cb', `edit failed for ${oldnote.id}: cannot renote itself`);
|
||||
}
|
||||
|
||||
// TODO create something like threadId but for quotes, that way we don't need full recursion
|
||||
const next = await this.notesRepository.findOne({ where: { id: renoteId }, select: { renoteId: true } });
|
||||
renoteId = next?.renoteId ?? null;
|
||||
}
|
||||
|
||||
switch (data.renote.visibility) {
|
||||
case 'public':
|
||||
// public noteは無条件にrenote可能
|
||||
|
|
@ -333,7 +321,7 @@ export class NoteEditService implements OnApplicationShutdown {
|
|||
case 'followers':
|
||||
// 他人のfollowers noteはreject
|
||||
if (data.renote.userId !== user.id) {
|
||||
throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is not public or home');
|
||||
throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Renote target is not public or home');
|
||||
}
|
||||
|
||||
// Renote対象がfollowersならfollowersにする
|
||||
|
|
@ -341,25 +329,44 @@ export class NoteEditService implements OnApplicationShutdown {
|
|||
break;
|
||||
case 'specified':
|
||||
// specified / direct noteはreject
|
||||
throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is not public or home');
|
||||
throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Renote target is not public or home');
|
||||
}
|
||||
|
||||
if (data.renote.userId !== user.id) {
|
||||
// Check local-only
|
||||
if (data.renote.localOnly && user.host != null) {
|
||||
throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Remote user cannot renote a local-only note');
|
||||
}
|
||||
|
||||
// Check visibility
|
||||
if (!await this.noteEntityService.isVisibleForMe(data.renote, user.id)) {
|
||||
throw new IdentifiableError('be9529e9-fe72-4de0-ae43-0b363c4938af', 'Cannot renote an invisible note');
|
||||
}
|
||||
|
||||
// Check blocking
|
||||
if (await this.userBlockingService.checkBlocked(data.renote.userId, user.id)) {
|
||||
throw new IdentifiableError('b6352a84-e5cd-4b05-a26c-63437a6b98ba', 'Renote target is blocked');
|
||||
}
|
||||
}
|
||||
|
||||
// Check for recursion
|
||||
if (data.renote.id === oldnote.id) {
|
||||
throw new IdentifiableError('33510210-8452-094c-6227-4a6c05d99f02', `edit failed for ${oldnote.id}: note cannot quote itself`);
|
||||
}
|
||||
for (let nextRenoteId = data.renote.renoteId; nextRenoteId != null;) {
|
||||
if (nextRenoteId === oldnote.id) {
|
||||
throw new IdentifiableError('ea93b7c2-3d6c-4e10-946b-00d50b1a75cb', `edit failed for ${oldnote.id}: note cannot quote a quote of itself`);
|
||||
}
|
||||
|
||||
// TODO create something like threadId but for quotes, that way we don't need full recursion
|
||||
const next = await this.notesRepository.findOne({ where: { id: nextRenoteId }, select: { renoteId: true } });
|
||||
nextRenoteId = next?.renoteId ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
// Check quote permissions
|
||||
await this.noteCreateService.checkQuotePermissions(data, user);
|
||||
|
||||
// Check blocking
|
||||
if (this.isRenote(data) && !this.isQuote(data)) {
|
||||
if (data.renote.userHost === null) {
|
||||
if (data.renote.userId !== user.id) {
|
||||
const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
|
||||
if (blocked) {
|
||||
throw new Error('blocked');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 返信対象がpublicではないならhomeにする
|
||||
if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') {
|
||||
data.visibility = 'home';
|
||||
|
|
|
|||
|
|
@ -280,13 +280,6 @@ export class ApNoteService {
|
|||
processErrors.push('quoteUnavailable');
|
||||
}
|
||||
|
||||
if (reply && reply.userHost == null && reply.localOnly) {
|
||||
throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Cannot reply to local-only note');
|
||||
}
|
||||
if (quote && quote.userHost == null && quote.localOnly) {
|
||||
throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Cannot quote a local-only note');
|
||||
}
|
||||
|
||||
// vote
|
||||
if (reply && reply.hasPoll) {
|
||||
const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id });
|
||||
|
|
@ -466,10 +459,6 @@ export class ApNoteService {
|
|||
processErrors.push('quoteUnavailable');
|
||||
}
|
||||
|
||||
if (quote && quote.userHost == null && quote.localOnly) {
|
||||
throw new IdentifiableError('12e23cec-edd9-442b-aa48-9c21f0c3b215', 'Cannot quote a local-only note');
|
||||
}
|
||||
|
||||
// vote
|
||||
if (reply && reply.hasPoll) {
|
||||
const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id });
|
||||
|
|
|
|||
|
|
@ -303,31 +303,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
if (renote == null) {
|
||||
throw new ApiError(meta.errors.noSuchRenoteTarget);
|
||||
} else if (isRenote(renote) && !isQuote(renote)) {
|
||||
throw new ApiError(meta.errors.cannotReRenote);
|
||||
} else if (!(await this.noteVisibilityService.checkNoteVisibilityAsync(renote, me)).accessible) {
|
||||
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
|
||||
}
|
||||
|
||||
// Check blocking
|
||||
if (renote.userId !== me.id) {
|
||||
const blockExist = await this.blockingsRepository.exists({
|
||||
where: {
|
||||
blockerId: renote.userId,
|
||||
blockeeId: me.id,
|
||||
},
|
||||
});
|
||||
if (blockExist) {
|
||||
throw new ApiError(meta.errors.youHaveBeenBlocked);
|
||||
}
|
||||
}
|
||||
|
||||
if (renote.visibility === 'followers' && renote.userId !== me.id) {
|
||||
// 他人のfollowers noteはreject
|
||||
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
|
||||
} else if (renote.visibility === 'specified') {
|
||||
// specified / direct noteはreject
|
||||
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
|
||||
}
|
||||
|
||||
if (renote.channelId && renote.channelId !== ps.channelId) {
|
||||
|
|
@ -351,26 +326,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
if (reply == null) {
|
||||
throw new ApiError(meta.errors.noSuchReplyTarget);
|
||||
} else if (isRenote(reply) && !isQuote(reply)) {
|
||||
throw new ApiError(meta.errors.cannotReplyToPureRenote);
|
||||
} else if (!(await this.noteVisibilityService.checkNoteVisibilityAsync(reply, me)).accessible) {
|
||||
throw new ApiError(meta.errors.cannotReplyToInvisibleNote);
|
||||
} else if (reply.visibility === 'specified' && ps.visibility !== 'specified') {
|
||||
throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility);
|
||||
}
|
||||
|
||||
// Check blocking
|
||||
if (reply.userId !== me.id) {
|
||||
const blockExist = await this.blockingsRepository.exists({
|
||||
where: {
|
||||
blockerId: reply.userId,
|
||||
blockeeId: me.id,
|
||||
},
|
||||
});
|
||||
if (blockExist) {
|
||||
throw new ApiError(meta.errors.youHaveBeenBlocked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ps.poll) {
|
||||
|
|
@ -430,6 +388,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.quoteDisabledForUser);
|
||||
} else if (e.id === 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a') {
|
||||
throw new ApiError(meta.errors.cannotReRenote);
|
||||
} else if (e.id === 'b6352a84-e5cd-4b05-a26c-63437a6b98ba') {
|
||||
throw new ApiError(meta.errors.youHaveBeenBlocked);
|
||||
} else if (e.id === 'be9529e9-fe72-4de0-ae43-0b363c4938af') {
|
||||
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
|
||||
} else if (e.id === '3ac74a84-8fd5-4bb0-870f-01804f82ce15') {
|
||||
throw new ApiError(meta.errors.cannotReplyToPureRenote);
|
||||
} else if (e.id === 'b98980fa-3780-406c-a935-b6d0eeee10d1') {
|
||||
throw new ApiError(meta.errors.cannotReplyToInvisibleNote);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
|
|
|
|||
|
|
@ -347,11 +347,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
|
||||
let renote: MiNote | null = null;
|
||||
|
||||
if (ps.renoteId === ps.editId) {
|
||||
throw new ApiError(meta.errors.cannotQuoteCurrentPost);
|
||||
}
|
||||
|
||||
if (ps.renoteId != null) {
|
||||
// Fetch renote to note
|
||||
renote = await this.notesRepository.findOneBy({ id: ps.renoteId });
|
||||
|
|
@ -360,27 +355,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.noSuchRenoteTarget);
|
||||
}
|
||||
|
||||
// Check blocking
|
||||
if (renote.userId !== me.id) {
|
||||
const blockExist = await this.blockingsRepository.exists({
|
||||
where: {
|
||||
blockerId: renote.userId,
|
||||
blockeeId: me.id,
|
||||
},
|
||||
});
|
||||
if (blockExist) {
|
||||
throw new ApiError(meta.errors.youHaveBeenBlocked);
|
||||
}
|
||||
}
|
||||
|
||||
if (renote.visibility === 'followers' && renote.userId !== me.id) {
|
||||
// 他人のfollowers noteはreject
|
||||
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
|
||||
} else if (renote.visibility === 'specified') {
|
||||
// specified / direct noteはreject
|
||||
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
|
||||
}
|
||||
|
||||
if (renote.channelId && renote.channelId !== ps.channelId) {
|
||||
// チャンネルのノートに対しリノート要求がきたとき、チャンネル外へのリノート可否をチェック
|
||||
// リノートのユースケースのうち、チャンネル内→チャンネル外は少数だと考えられるため、JOINはせず必要な時に都度取得する
|
||||
|
|
@ -402,26 +376,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
if (reply == null) {
|
||||
throw new ApiError(meta.errors.noSuchReplyTarget);
|
||||
} else if (isRenote(reply) && !isQuote(reply)) {
|
||||
throw new ApiError(meta.errors.cannotReplyToPureRenote);
|
||||
} else if (!(await this.noteVisibilityService.checkNoteVisibilityAsync(reply, me)).accessible) {
|
||||
throw new ApiError(meta.errors.cannotReplyToInvisibleNote);
|
||||
} else if (reply.visibility === 'specified' && ps.visibility !== 'specified') {
|
||||
throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility);
|
||||
}
|
||||
|
||||
// Check blocking
|
||||
if (reply.userId !== me.id) {
|
||||
const blockExist = await this.blockingsRepository.exists({
|
||||
where: {
|
||||
blockerId: reply.userId,
|
||||
blockeeId: me.id,
|
||||
},
|
||||
});
|
||||
if (blockExist) {
|
||||
throw new ApiError(meta.errors.youHaveBeenBlocked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ps.poll) {
|
||||
|
|
@ -477,10 +434,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new ApiError(meta.errors.containsTooManyMentions);
|
||||
} else if (e.id === '1c0ea108-d1e3-4e8e-aa3f-4d2487626153') {
|
||||
throw new ApiError(meta.errors.quoteDisabledForUser);
|
||||
} else if (e.id === '33510210-8452-094c-6227-4a6c05d99f02') {
|
||||
throw new ApiError(meta.errors.cannotQuoteCurrentPost);
|
||||
} else if (e.id === 'ea93b7c2-3d6c-4e10-946b-00d50b1a75cb') {
|
||||
throw new ApiError(meta.errors.cannotQuoteaQuoteOfCurrentPost);
|
||||
} else if (e.id === 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a') {
|
||||
throw new ApiError(meta.errors.cannotReRenote);
|
||||
} else if (e.id === 'b6352a84-e5cd-4b05-a26c-63437a6b98ba') {
|
||||
throw new ApiError(meta.errors.youHaveBeenBlocked);
|
||||
} else if (e.id === 'be9529e9-fe72-4de0-ae43-0b363c4938af') {
|
||||
throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
|
||||
} else if (e.id === '3ac74a84-8fd5-4bb0-870f-01804f82ce15') {
|
||||
throw new ApiError(meta.errors.cannotReplyToPureRenote);
|
||||
} else if (e.id === 'b98980fa-3780-406c-a935-b6d0eeee10d1') {
|
||||
throw new ApiError(meta.errors.cannotReplyToInvisibleNote);
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue