From 97edaca3510572956abb98ac099cea3c1786d9fb Mon Sep 17 00:00:00 2001
From: Johann150
Date: Tue, 8 Mar 2022 21:16:51 +0100
Subject: [PATCH 1/9] getNote checks visibility
Raise an error When a note is not visible to the requesting user.
---
packages/backend/src/server/api/common/getters.ts | 14 +++++++++++---
.../src/server/api/endpoints/admin/promo/create.ts | 6 +++---
.../src/server/api/endpoints/clips/add-note.ts | 6 +++---
.../src/server/api/endpoints/notes/clips.ts | 6 +++---
.../src/server/api/endpoints/notes/conversation.ts | 6 +++---
.../src/server/api/endpoints/notes/delete.ts | 6 +++---
.../server/api/endpoints/notes/favorites/create.ts | 6 +++---
.../server/api/endpoints/notes/favorites/delete.ts | 6 +++---
.../src/server/api/endpoints/notes/polls/vote.ts | 6 +++---
.../src/server/api/endpoints/notes/reactions.ts | 8 ++++++++
.../server/api/endpoints/notes/reactions/create.ts | 6 +++---
.../server/api/endpoints/notes/reactions/delete.ts | 6 +++---
.../src/server/api/endpoints/notes/renotes.ts | 6 +++---
.../backend/src/server/api/endpoints/notes/show.ts | 6 +++---
.../api/endpoints/notes/thread-muting/create.ts | 6 +++---
.../api/endpoints/notes/thread-muting/delete.ts | 6 +++---
.../src/server/api/endpoints/notes/translate.ts | 10 +++-------
.../src/server/api/endpoints/notes/unrenote.ts | 6 +++---
.../server/api/endpoints/notes/watching/create.ts | 6 +++---
.../server/api/endpoints/notes/watching/delete.ts | 6 +++---
.../backend/src/server/api/endpoints/promo/read.ts | 6 +++---
21 files changed, 76 insertions(+), 64 deletions(-)
diff --git a/packages/backend/src/server/api/common/getters.ts b/packages/backend/src/server/api/common/getters.ts
index 783ea9ef7..c5a1e765e 100644
--- a/packages/backend/src/server/api/common/getters.ts
+++ b/packages/backend/src/server/api/common/getters.ts
@@ -2,12 +2,20 @@ import { IdentifiableError } from '@/misc/identifiable-error.js';
import { User } from '@/models/entities/user.js';
import { Note } from '@/models/entities/note.js';
import { Notes, Users } from '@/models/index.js';
+import { generateVisibilityQuery } from './generate-visibility-query.js';
/**
- * Get note for API processing
+ * Get note for API processing, taking into account visibility.
*/
-export async function getNote(noteId: Note['id']) {
- const note = await Notes.findOneBy({ id: noteId });
+export async function getNote(noteId: Note['id'], me: { id: User['id'] } | null) {
+ const query = Notes.createQueryBuilder('note')
+ .where("note.id = :id", {
+ id: noteId,
+ });
+
+ generateVisibilityQuery(query, me);
+
+ const note = await query.getOne();
if (note == null) {
throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.');
diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts
index 68a17867b..b5142fcf0 100644
--- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts
@@ -35,9 +35,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
const exist = await PromoNotes.findOneBy({ noteId: note.id });
diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts
index 5d72f5c1b..91baa8eb7 100644
--- a/packages/backend/src/server/api/endpoints/clips/add-note.ts
+++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts
@@ -52,9 +52,9 @@ export default define(meta, paramDef, async (ps, user) => {
throw new ApiError(meta.errors.noSuchClip);
}
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
const exist = await ClipNotes.findOneBy({
diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts
index e79f8563e..976c11260 100644
--- a/packages/backend/src/server/api/endpoints/notes/clips.ts
+++ b/packages/backend/src/server/api/endpoints/notes/clips.ts
@@ -38,9 +38,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, me) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, me).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
const clipNotes = await ClipNotes.findBy({
diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts
index b731d1824..f939d7a6a 100644
--- a/packages/backend/src/server/api/endpoints/notes/conversation.ts
+++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts
@@ -40,9 +40,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
const conversation: Note[] = [];
diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts
index 9c3ca136c..34d23448e 100644
--- a/packages/backend/src/server/api/endpoints/notes/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/delete.ts
@@ -43,9 +43,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
if ((!user.isAdmin && !user.isModerator) && (note.userId !== user.id)) {
diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
index 097371a42..b5dd88a4e 100644
--- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
@@ -37,9 +37,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
// Get favoritee
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
// if already favorited
diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts
index 82ef4fa19..3f4d39254 100644
--- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts
@@ -36,9 +36,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
// Get favoritee
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
// if already favorited
diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
index 45a832cbd..6dd5ddf9e 100644
--- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
@@ -72,9 +72,9 @@ export default define(meta, paramDef, async (ps, user) => {
const createdAt = new Date();
// Get votee
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
if (!note.hasPoll) {
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts
index d56bad965..02a288138 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts
@@ -2,6 +2,8 @@ import { FindOptionsWhere } from 'typeorm';
import { NoteReactions } from '@/models/index.js';
import { NoteReaction } from '@/models/entities/note-reaction.js';
import define from '../../define.js';
+import { ApiError } from '../../error.js';
+import { getNote } from '../../common/getters.js';
export const meta = {
tags: ['notes', 'reactions'],
@@ -45,6 +47,12 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
+ // check note visibility
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
+
const query = {
noteId: ps.noteId,
} as FindOptionsWhere;
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
index 07e52a926..b5c0c9d17 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
@@ -42,9 +42,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
await createReaction(user, note, ps.reaction).catch(e => {
if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError(meta.errors.alreadyReacted);
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
index 83fdf6959..c25d88d1b 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
@@ -42,9 +42,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
await deleteReaction(user, note).catch(e => {
if (e.id === '60527ec9-b4cb-4a88-a6bd-32d3ad26817d') throw new ApiError(meta.errors.notReacted);
diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts
index 28be36076..1fa9c5230 100644
--- a/packages/backend/src/server/api/endpoints/notes/renotes.ts
+++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts
@@ -44,9 +44,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts
index 5cd74bd2c..0f5d0c942 100644
--- a/packages/backend/src/server/api/endpoints/notes/show.ts
+++ b/packages/backend/src/server/api/endpoints/notes/show.ts
@@ -33,9 +33,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
return await Notes.pack(note, user, {
diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
index cf360526d..4154b5dc5 100644
--- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
@@ -31,9 +31,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
const mutedNotes = await Notes.find({
diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts
index ac310d0fe..cbc0e5ce5 100644
--- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts
@@ -29,9 +29,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
await NoteThreadMutings.delete({
diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts
index 5e40e7106..c42bf8c65 100644
--- a/packages/backend/src/server/api/endpoints/notes/translate.ts
+++ b/packages/backend/src/server/api/endpoints/notes/translate.ts
@@ -38,15 +38,11 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
- if (!(await Notes.isVisibleForMe(note, user ? user.id : null))) {
- return 204; // TODO: 良い感じのエラー返す
- }
-
if (note.text == null) {
return 204;
}
diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts
index 1c7fa6812..1089a9e37 100644
--- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts
@@ -37,9 +37,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
const renotes = await Notes.findBy({
diff --git a/packages/backend/src/server/api/endpoints/notes/watching/create.ts b/packages/backend/src/server/api/endpoints/notes/watching/create.ts
index 7d482b073..6025799fa 100644
--- a/packages/backend/src/server/api/endpoints/notes/watching/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/watching/create.ts
@@ -29,9 +29,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
await watch(user.id, note);
diff --git a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts
index 2c1a2e5fb..7021c7970 100644
--- a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts
@@ -29,9 +29,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
await unwatch(user.id, note);
diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts
index c6a940c65..7c37fcbf7 100644
--- a/packages/backend/src/server/api/endpoints/promo/read.ts
+++ b/packages/backend/src/server/api/endpoints/promo/read.ts
@@ -28,9 +28,9 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await getNote(ps.noteId).catch(e => {
- if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
- throw e;
+ const note = await getNote(ps.noteId, user).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
const exist = await PromoReads.findOneBy({
From 3c6d9cc8ab61740fb2497a9ee88d8aa97e05b50b Mon Sep 17 00:00:00 2001
From: Johann150
Date: Tue, 8 Mar 2022 21:17:58 +0100
Subject: [PATCH 2/9] use getNote instead of Notes.find
If a note is not visible to the requesting user, an error will be raised.
---
.../api/endpoints/notes/conversation.ts | 6 +++++-
.../src/server/api/endpoints/notes/create.ts | 19 +++++++++++--------
.../src/server/api/endpoints/notes/state.ts | 3 ++-
3 files changed, 18 insertions(+), 10 deletions(-)
diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts
index f939d7a6a..7ee052001 100644
--- a/packages/backend/src/server/api/endpoints/notes/conversation.ts
+++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts
@@ -50,7 +50,11 @@ export default define(meta, paramDef, async (ps, user) => {
async function get(id: any) {
i++;
- const p = await Notes.findOneBy({ id });
+ const p = await getNote(id, user).catch(e => {
+ if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') return null;
+ throw e;
+ });
+
if (p == null) return;
if (i > ps.offset!) {
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index aeac02d39..82540f96b 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -10,6 +10,7 @@ import { noteVisibilities } from '../../../../types.js';
import { ApiError } from '../../error.js';
import define from '../../define.js';
import { HOUR } from '@/const.js';
+import { getNote } from '../../common/getters.js';
export const meta = {
tags: ['notes'],
@@ -185,11 +186,12 @@ export default define(meta, paramDef, async (ps, user) => {
let renote: Note | null = null;
if (ps.renoteId != null) {
// Fetch renote to note
- renote = await Notes.findOneBy({ id: ps.renoteId });
+ renote = await getNote(ps.renoteId, user).catch(e => {
+ if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchRenoteTarget);
+ throw e;
+ });
- if (renote == null) {
- throw new ApiError(meta.errors.noSuchRenoteTarget);
- } else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) {
+ if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) {
throw new ApiError(meta.errors.cannotReRenote);
}
@@ -208,11 +210,12 @@ export default define(meta, paramDef, async (ps, user) => {
let reply: Note | null = null;
if (ps.replyId != null) {
// Fetch reply
- reply = await Notes.findOneBy({ id: ps.replyId });
+ reply = await getNote(ps.replyId, user).catch(e => {
+ if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchReplyTarget);
+ throw e;
+ });
- if (reply == null) {
- throw new ApiError(meta.errors.noSuchReplyTarget);
- } else if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) {
+ if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) {
throw new ApiError(meta.errors.cannotReplyToPureRenote);
}
diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts
index 01afa5add..67579b2a6 100644
--- a/packages/backend/src/server/api/endpoints/notes/state.ts
+++ b/packages/backend/src/server/api/endpoints/notes/state.ts
@@ -1,4 +1,5 @@
import { NoteFavorites, Notes, NoteThreadMutings, NoteWatchings } from '@/models/index.js';
+import { getNote } from '../../common/getters.js';
import define from '../../define.js';
export const meta = {
@@ -36,7 +37,7 @@ export const paramDef = {
// eslint-disable-next-line import/no-default-export
export default define(meta, paramDef, async (ps, user) => {
- const note = await Notes.findOneByOrFail({ id: ps.noteId });
+ const note = await getNote(ps.noteId, user);
const [favorite, watching, threadMuting] = await Promise.all([
NoteFavorites.count({
From 2486eff74710b2b7a52058cfee8cf0716eba9e30 Mon Sep 17 00:00:00 2001
From: Johann150
Date: Fri, 22 Apr 2022 13:35:02 +0200
Subject: [PATCH 3/9] packing notes not visible to user raises an error
Instead of just hiding specific fields, the entire note is hidden. This means
that metadata of the note such as who is the author, when was it sent are
completely hidden.
---
.../backend/src/models/repositories/note.ts | 77 +++----------------
1 file changed, 9 insertions(+), 68 deletions(-)
diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts
index 3fefab031..e697b4cea 100644
--- a/packages/backend/src/models/repositories/note.ts
+++ b/packages/backend/src/models/repositories/note.ts
@@ -10,66 +10,7 @@ import { convertLegacyReaction, convertLegacyReactions, decodeReaction } from '@
import { NoteReaction } from '@/models/entities/note-reaction.js';
import { aggregateNoteEmojis, populateEmojis, prefetchEmojis } from '@/misc/populate-emojis.js';
import { db } from '@/db/postgre.js';
-
-async function hideNote(packedNote: Packed<'Note'>, meId: User['id'] | null) {
- // TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
- let hide = false;
-
- // visibility が specified かつ自分が指定されていなかったら非表示
- if (packedNote.visibility === 'specified') {
- if (meId == null) {
- hide = true;
- } else if (meId === packedNote.userId) {
- hide = false;
- } else {
- // 指定されているかどうか
- const specified = packedNote.visibleUserIds!.some((id: any) => meId === id);
-
- if (specified) {
- hide = false;
- } else {
- hide = true;
- }
- }
- }
-
- // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
- if (packedNote.visibility === 'followers') {
- if (meId == null) {
- hide = true;
- } else if (meId === packedNote.userId) {
- hide = false;
- } else if (packedNote.reply && (meId === packedNote.reply.userId)) {
- // 自分の投稿に対するリプライ
- hide = false;
- } else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
- // 自分へのメンション
- hide = false;
- } else {
- // フォロワーかどうか
- const following = await Followings.findOneBy({
- followeeId: packedNote.userId,
- followerId: meId,
- });
-
- if (following == null) {
- hide = true;
- } else {
- hide = false;
- }
- }
- }
-
- if (hide) {
- packedNote.visibleUserIds = undefined;
- packedNote.fileIds = [];
- packedNote.files = [];
- packedNote.text = null;
- packedNote.poll = undefined;
- packedNote.cw = null;
- packedNote.isHidden = true;
- }
-}
+import { IdentifiableError } from '@/misc/identifiable-error.js';
async function populatePoll(note: Note, meId: User['id'] | null) {
const poll = await Polls.findOneByOrFail({ noteId: note.id });
@@ -193,7 +134,6 @@ export const NoteRepository = db.getRepository(Note).extend({
me?: { id: User['id'] } | null | undefined,
options?: {
detail?: boolean;
- skipHide?: boolean;
_hint_?: {
myReactions: Map;
};
@@ -201,13 +141,16 @@ export const NoteRepository = db.getRepository(Note).extend({
): Promise> {
const opts = Object.assign({
detail: true,
- skipHide: false,
}, options);
const meId = me ? me.id : null;
const note = typeof src === 'object' ? src : await this.findOneByOrFail({ id: src });
const host = note.userHost;
+ if (!await this.isVisibleForMe(note, meId)) {
+ throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.');
+ }
+
let text = note.text;
if (note.name && (note.url ?? note.uri)) {
@@ -282,10 +225,6 @@ export const NoteRepository = db.getRepository(Note).extend({
packed.text = mfm.toString(tokens);
}
- if (!opts.skipHide) {
- await hideNote(packed, meId);
- }
-
return packed;
},
@@ -294,7 +233,6 @@ export const NoteRepository = db.getRepository(Note).extend({
me?: { id: User['id'] } | null | undefined,
options?: {
detail?: boolean;
- skipHide?: boolean;
}
) {
if (notes.length === 0) return [];
@@ -316,11 +254,14 @@ export const NoteRepository = db.getRepository(Note).extend({
await prefetchEmojis(aggregateNoteEmojis(notes));
- return await Promise.all(notes.map(n => this.pack(n, me, {
+ const promises = await Promise.allSettled(notes.map(n => this.pack(n, me, {
...options,
_hint_: {
myReactions: myReactionsMap,
},
})));
+
+ // filter out rejected promises, only keep fulfilled values
+ return promises.flatMap(result => result.status === 'fulfilled' ? [result.value] : []);
},
});
From c6192ac95a21590276e53ba2bb4f26af0026e2f6 Mon Sep 17 00:00:00 2001
From: Johann150
Date: Tue, 24 May 2022 09:48:33 +0200
Subject: [PATCH 4/9] fix: handle exception in note favorites
---
packages/backend/src/models/repositories/note-favorite.ts | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/backend/src/models/repositories/note-favorite.ts b/packages/backend/src/models/repositories/note-favorite.ts
index 9bd97f988..1d5702053 100644
--- a/packages/backend/src/models/repositories/note-favorite.ts
+++ b/packages/backend/src/models/repositories/note-favorite.ts
@@ -14,6 +14,7 @@ export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({
id: favorite.id,
createdAt: favorite.createdAt.toISOString(),
noteId: favorite.noteId,
+ // may throw error
note: await Notes.pack(favorite.note || favorite.noteId, me),
};
},
@@ -22,6 +23,7 @@ export const NoteFavoriteRepository = db.getRepository(NoteFavorite).extend({
favorites: any[],
me: { id: User['id'] }
) {
- return Promise.all(favorites.map(x => this.pack(x, me)));
+ return Promise.allSettled(favorites.map(x => this.pack(x, me)))
+ .then(promises => promises.flatMap(result => result.status === 'fulfilled' ? [result.value] : []));
},
});
From cfa371b52b980991946ce0f3353479f0cc26dcc6 Mon Sep 17 00:00:00 2001
From: Johann150
Date: Tue, 24 May 2022 10:00:46 +0200
Subject: [PATCH 5/9] refactor: remove note re-packing in streaming API
Instead of packing the note for public user before passing it to
streams, the note is now either packed for the user the respective
stream belongs to (`mainStream`) or not packed at all and then packed
later (`notesStream`).
Because this is a new common task between different channels, a shared
implementation of packing a note from notesStream is created. This
implementation will simply skip a note if it is not visible to the user
that the channel belongs to.
---
.../backend/src/server/api/stream/channel.ts | 30 +++++++++++++++++++
.../src/server/api/stream/channels/antenna.ts | 24 ++++++++++-----
.../src/server/api/stream/channels/channel.ts | 17 ++---------
.../api/stream/channels/global-timeline.ts | 16 +---------
.../src/server/api/stream/channels/hashtag.ts | 10 +------
.../api/stream/channels/home-timeline.ts | 26 +---------------
.../api/stream/channels/hybrid-timeline.ts | 26 +---------------
.../api/stream/channels/local-timeline.ts | 16 +---------
.../src/server/api/stream/channels/main.ts | 15 ----------
.../server/api/stream/channels/user-list.ts | 27 ++---------------
.../backend/src/server/api/stream/types.ts | 2 +-
packages/backend/src/services/note/create.ts | 29 +++++++++---------
packages/backend/src/services/stream.ts | 3 +-
13 files changed, 72 insertions(+), 169 deletions(-)
diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts
index d2cc5122d..c9cffd2d3 100644
--- a/packages/backend/src/server/api/stream/channel.ts
+++ b/packages/backend/src/server/api/stream/channel.ts
@@ -1,4 +1,8 @@
import Connection from '.';
+import { Note } from '@/models/entities/note.js';
+import { Notes } from '@/models/index.js';
+import { Packed } from '@/misc/schema.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
/**
* Stream channel
@@ -54,6 +58,32 @@ export default abstract class Channel {
});
}
+ protected withPackedNote(callback: (note: Packed<'Note'>) => void): (Note) => void {
+ return async (note: Note) => {
+ try {
+ // because `note` was previously JSON.stringify'ed, the fields that
+ // were objects before are now strings and have to be restored or
+ // removed from the object
+ note.createdAt = new Date(note.createdAt);
+ delete note.reply;
+ delete note.renote;
+ delete note.user;
+ delete note.channel;
+
+ const packed = await Notes.pack(note, this.user, { detail: true });
+
+ callback(packed);
+ } catch (err) {
+ if (err instanceof IdentifiableError && err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') {
+ // skip: note not visible to user
+ return;
+ } else {
+ throw err;
+ }
+ }
+ };
+ }
+
public abstract init(params: any): void;
public dispose?(): void;
public onMessage?(type: string, body: any): void;
diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts
index d28320d92..a9a98e904 100644
--- a/packages/backend/src/server/api/stream/channels/antenna.ts
+++ b/packages/backend/src/server/api/stream/channels/antenna.ts
@@ -2,6 +2,7 @@ import Channel from '../channel.js';
import { Notes } from '@/models/index.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { StreamMessages } from '../types.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
export default class extends Channel {
public readonly chName = 'antenna';
@@ -23,16 +24,25 @@ export default class extends Channel {
private async onEvent(data: StreamMessages['antenna']['payload']) {
if (data.type === 'note') {
- const note = await Notes.pack(data.body.id, this.user, { detail: true });
+ try {
+ const note = await Notes.pack(data.body.id, this.user, { detail: true });
- // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.muting)) return;
- // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.blocking)) return;
+ // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.muting)) return;
+ // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
+ if (isUserRelated(note, this.blocking)) return;
- this.connection.cacheNote(note);
+ this.connection.cacheNote(note);
- this.send('note', note);
+ this.send('note', note);
+ } catch (e) {
+ if (e instanceof IdentifiableError && e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') {
+ // skip: note not visible to user
+ return;
+ } else {
+ throw e;
+ }
+ }
} else {
this.send(data.type, data.body);
}
diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts
index 5148cfd05..5a26ea7bf 100644
--- a/packages/backend/src/server/api/stream/channels/channel.ts
+++ b/packages/backend/src/server/api/stream/channels/channel.ts
@@ -1,5 +1,5 @@
import Channel from '../channel.js';
-import { Notes, Users } from '@/models/index.js';
+import { Users } from '@/models/index.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { User } from '@/models/entities/user.js';
import { StreamMessages } from '../types.js';
@@ -15,7 +15,7 @@ export default class extends Channel {
constructor(id: string, connection: Channel['connection']) {
super(id, connection);
- this.onNote = this.onNote.bind(this);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
}
public async init(params: any) {
@@ -30,19 +30,6 @@ export default class extends Channel {
private async onNote(note: Packed<'Note'>) {
if (note.channelId !== this.channelId) return;
- // リプライなら再pack
- if (note.replyId != null) {
- note.reply = await Notes.pack(note.replyId, this.user, {
- detail: true,
- });
- }
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await Notes.pack(note.renoteId, this.user, {
- detail: true,
- });
- }
-
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.muting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts
index 5b4ae850e..391851ecd 100644
--- a/packages/backend/src/server/api/stream/channels/global-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts
@@ -1,6 +1,5 @@
import Channel from '../channel.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
-import { Notes } from '@/models/index.js';
import { checkWordMute } from '@/misc/check-word-mute.js';
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
import { isUserRelated } from '@/misc/is-user-related.js';
@@ -13,7 +12,7 @@ export default class extends Channel {
constructor(id: string, connection: Channel['connection']) {
super(id, connection);
- this.onNote = this.onNote.bind(this);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
}
public async init(params: any) {
@@ -30,19 +29,6 @@ export default class extends Channel {
if (note.visibility !== 'public') return;
if (note.channelId != null) return;
- // リプライなら再pack
- if (note.replyId != null) {
- note.reply = await Notes.pack(note.replyId, this.user, {
- detail: true,
- });
- }
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await Notes.pack(note.renoteId, this.user, {
- detail: true,
- });
- }
-
// 関係ない返信は除外
if (note.reply && !this.user!.showTimelineReplies) {
const reply = note.reply;
diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts
index 741db447e..f9f7ae410 100644
--- a/packages/backend/src/server/api/stream/channels/hashtag.ts
+++ b/packages/backend/src/server/api/stream/channels/hashtag.ts
@@ -1,5 +1,4 @@
import Channel from '../channel.js';
-import { Notes } from '@/models/index.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { Packed } from '@/misc/schema.js';
@@ -12,7 +11,7 @@ export default class extends Channel {
constructor(id: string, connection: Channel['connection']) {
super(id, connection);
- this.onNote = this.onNote.bind(this);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
}
public async init(params: any) {
@@ -29,13 +28,6 @@ export default class extends Channel {
const matched = this.q.some(tags => tags.every(tag => noteTags.includes(normalizeForSearch(tag))));
if (!matched) return;
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await Notes.pack(note.renoteId, this.user, {
- detail: true,
- });
- }
-
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.muting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts
index 075a242ef..9f5188547 100644
--- a/packages/backend/src/server/api/stream/channels/home-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts
@@ -1,5 +1,4 @@
import Channel from '../channel.js';
-import { Notes } from '@/models/index.js';
import { checkWordMute } from '@/misc/check-word-mute.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
@@ -12,7 +11,7 @@ export default class extends Channel {
constructor(id: string, connection: Channel['connection']) {
super(id, connection);
- this.onNote = this.onNote.bind(this);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
}
public async init(params: any) {
@@ -31,29 +30,6 @@ export default class extends Channel {
// Ignore notes from instances the user has muted
if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return;
- if (['followers', 'specified'].includes(note.visibility)) {
- note = await Notes.pack(note.id, this.user!, {
- detail: true,
- });
-
- if (note.isHidden) {
- return;
- }
- } else {
- // リプライなら再pack
- if (note.replyId != null) {
- note.reply = await Notes.pack(note.replyId, this.user!, {
- detail: true,
- });
- }
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await Notes.pack(note.renoteId, this.user!, {
- detail: true,
- });
- }
- }
-
// 関係ない返信は除外
if (note.reply && !this.user!.showTimelineReplies) {
const reply = note.reply;
diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
index f5dedf77c..e73136b8e 100644
--- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
@@ -1,6 +1,5 @@
import Channel from '../channel.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
-import { Notes } from '@/models/index.js';
import { checkWordMute } from '@/misc/check-word-mute.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
@@ -13,7 +12,7 @@ export default class extends Channel {
constructor(id: string, connection: Channel['connection']) {
super(id, connection);
- this.onNote = this.onNote.bind(this);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
}
public async init(params: any) {
@@ -36,29 +35,6 @@ export default class extends Channel {
(note.channelId != null && this.followingChannels.has(note.channelId))
)) return;
- if (['followers', 'specified'].includes(note.visibility)) {
- note = await Notes.pack(note.id, this.user!, {
- detail: true,
- });
-
- if (note.isHidden) {
- return;
- }
- } else {
- // リプライなら再pack
- if (note.replyId != null) {
- note.reply = await Notes.pack(note.replyId, this.user!, {
- detail: true,
- });
- }
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await Notes.pack(note.renoteId, this.user!, {
- detail: true,
- });
- }
- }
-
// Ignore notes from instances the user has muted
if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return;
diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts
index f01f47723..729de6d4a 100644
--- a/packages/backend/src/server/api/stream/channels/local-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts
@@ -1,6 +1,5 @@
import Channel from '../channel.js';
import { fetchMeta } from '@/misc/fetch-meta.js';
-import { Notes } from '@/models/index.js';
import { checkWordMute } from '@/misc/check-word-mute.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { Packed } from '@/misc/schema.js';
@@ -12,7 +11,7 @@ export default class extends Channel {
constructor(id: string, connection: Channel['connection']) {
super(id, connection);
- this.onNote = this.onNote.bind(this);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
}
public async init(params: any) {
@@ -30,19 +29,6 @@ export default class extends Channel {
if (note.visibility !== 'public') return;
if (note.channelId != null && !this.followingChannels.has(note.channelId)) return;
- // リプライなら再pack
- if (note.replyId != null) {
- note.reply = await Notes.pack(note.replyId, this.user, {
- detail: true,
- });
- }
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await Notes.pack(note.renoteId, this.user, {
- detail: true,
- });
- }
-
// 関係ない返信は除外
if (note.reply && !this.user!.showTimelineReplies) {
const reply = note.reply;
diff --git a/packages/backend/src/server/api/stream/channels/main.ts b/packages/backend/src/server/api/stream/channels/main.ts
index 9cfea0bfc..7f42263db 100644
--- a/packages/backend/src/server/api/stream/channels/main.ts
+++ b/packages/backend/src/server/api/stream/channels/main.ts
@@ -1,5 +1,4 @@
import Channel from '../channel.js';
-import { Notes } from '@/models/index.js';
import { isInstanceMuted, isUserFromMutedInstance } from '@/misc/is-instance-muted.js';
export default class extends Channel {
@@ -16,26 +15,12 @@ export default class extends Channel {
if (isUserFromMutedInstance(data.body, new Set(this.userProfile?.mutedInstances ?? []))) return;
if (data.body.userId && this.muting.has(data.body.userId)) return;
- if (data.body.note && data.body.note.isHidden) {
- const note = await Notes.pack(data.body.note.id, this.user, {
- detail: true,
- });
- this.connection.cacheNote(note);
- data.body.note = note;
- }
break;
}
case 'mention': {
if (isInstanceMuted(data.body, new Set(this.userProfile?.mutedInstances ?? []))) return;
if (this.muting.has(data.body.userId)) return;
- if (data.body.isHidden) {
- const note = await Notes.pack(data.body.id, this.user, {
- detail: true,
- });
- this.connection.cacheNote(note);
- data.body = note;
- }
break;
}
}
diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts
index 97ad2983c..9b2476148 100644
--- a/packages/backend/src/server/api/stream/channels/user-list.ts
+++ b/packages/backend/src/server/api/stream/channels/user-list.ts
@@ -1,5 +1,5 @@
import Channel from '../channel.js';
-import { Notes, UserListJoinings, UserLists } from '@/models/index.js';
+import { UserListJoinings, UserLists } from '@/models/index.js';
import { User } from '@/models/entities/user.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { Packed } from '@/misc/schema.js';
@@ -15,7 +15,7 @@ export default class extends Channel {
constructor(id: string, connection: Channel['connection']) {
super(id, connection);
this.updateListUsers = this.updateListUsers.bind(this);
- this.onNote = this.onNote.bind(this);
+ this.onNote = this.withPackedNote(this.onNote.bind(this));
}
public async init(params: any) {
@@ -51,29 +51,6 @@ export default class extends Channel {
private async onNote(note: Packed<'Note'>) {
if (!this.listUsers.includes(note.userId)) return;
- if (['followers', 'specified'].includes(note.visibility)) {
- note = await Notes.pack(note.id, this.user, {
- detail: true,
- });
-
- if (note.isHidden) {
- return;
- }
- } else {
- // リプライなら再pack
- if (note.replyId != null) {
- note.reply = await Notes.pack(note.replyId, this.user, {
- detail: true,
- });
- }
- // Renoteなら再pack
- if (note.renoteId != null) {
- note.renote = await Notes.pack(note.renoteId, this.user, {
- detail: true,
- });
- }
- }
-
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.muting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts
index 5fdc6dfcf..b969535ca 100644
--- a/packages/backend/src/server/api/stream/types.ts
+++ b/packages/backend/src/server/api/stream/types.ts
@@ -243,7 +243,7 @@ export type StreamMessages = {
};
notes: {
name: 'notesStream';
- payload: Packed<'Note'>;
+ payload: Note;
};
};
diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts
index 865956746..61a811d70 100644
--- a/packages/backend/src/services/note/create.ts
+++ b/packages/backend/src/services/note/create.ts
@@ -345,19 +345,15 @@ export default async (user: { id: User['id']; username: User['username']; host:
}
}
- // Pack the note
- const noteObj = await Notes.pack(note);
+ publishNotesStream(note);
- publishNotesStream(noteObj);
+ const webhooks = await getActiveWebhooks().then(webhooks => webhooks.filter(x => x.userId === user.id && x.on.includes('note')));
- getActiveWebhooks().then(webhooks => {
- webhooks = webhooks.filter(x => x.userId === user.id && x.on.includes('note'));
- for (const webhook of webhooks) {
- webhookDeliver(webhook, 'note', {
- note: noteObj,
- });
- }
- });
+ for (const webhook of webhooks) {
+ webhookDeliver(webhook, 'note', {
+ note: await Notes.pack(note, user),
+ });
+ }
const nm = new NotificationManager(user, note);
const nmRelatedPromises = [];
@@ -378,12 +374,14 @@ export default async (user: { id: User['id']; username: User['username']; host:
if (!threadMuted) {
nm.push(data.reply.userId, 'reply');
- publishMainStream(data.reply.userId, 'reply', noteObj);
+
+ const packedReply = await Notes.pack(note, { id: data.reply.userId });
+ publishMainStream(data.reply.userId, 'reply', packedReply);
const webhooks = (await getActiveWebhooks()).filter(x => x.userId === data.reply!.userId && x.on.includes('reply'));
for (const webhook of webhooks) {
webhookDeliver(webhook, 'reply', {
- note: noteObj,
+ note: packedReply,
});
}
}
@@ -404,12 +402,13 @@ export default async (user: { id: User['id']; username: User['username']; host:
// Publish event
if ((user.id !== data.renote.userId) && data.renote.userHost === null) {
- publishMainStream(data.renote.userId, 'renote', noteObj);
+ const packedRenote = await Notes.pack(note, { id: data.renote.userId });
+ publishMainStream(data.renote.userId, 'renote', packedRenote);
const webhooks = (await getActiveWebhooks()).filter(x => x.userId === data.renote!.userId && x.on.includes('renote'));
for (const webhook of webhooks) {
webhookDeliver(webhook, 'renote', {
- note: noteObj,
+ note: packedRenote,
});
}
}
diff --git a/packages/backend/src/services/stream.ts b/packages/backend/src/services/stream.ts
index 9fa2b9713..4895bbace 100644
--- a/packages/backend/src/services/stream.ts
+++ b/packages/backend/src/services/stream.ts
@@ -22,7 +22,6 @@ import {
UserListStreamTypes,
UserStreamTypes,
} from '@/server/api/stream/types.js';
-import { Packed } from '@/misc/schema.js';
class Publisher {
private publish = (channel: StreamChannels, type: string | null, value?: any): void => {
@@ -87,7 +86,7 @@ class Publisher {
this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value);
};
- public publishNotesStream = (note: Packed<'Note'>): void => {
+ public publishNotesStream = (note: Note): void => {
this.publish('notesStream', null, note);
};
From 128d0f0d4e2b9d8b99108ef550c4f63f6329e03d Mon Sep 17 00:00:00 2001
From: Johann150
Date: Tue, 24 May 2022 10:08:57 +0200
Subject: [PATCH 6/9] remove isHidden and its uses
The `isHidden` attribute is not being set any more and is thus removed.
Handling in the client is no longer necessary.
---
packages/backend/src/misc/get-note-summary.ts | 4 ----
packages/backend/src/models/schema/note.ts | 4 ----
packages/client/src/components/note-detailed.vue | 1 -
packages/client/src/components/note.vue | 1 -
packages/client/src/components/sub-note-content.vue | 1 -
packages/client/src/scripts/get-note-summary.ts | 4 ----
6 files changed, 15 deletions(-)
diff --git a/packages/backend/src/misc/get-note-summary.ts b/packages/backend/src/misc/get-note-summary.ts
index 3f35ccee8..45fb50964 100644
--- a/packages/backend/src/misc/get-note-summary.ts
+++ b/packages/backend/src/misc/get-note-summary.ts
@@ -9,10 +9,6 @@ export const getNoteSummary = (note: Packed<'Note'>): string => {
return `(❌⛔)`;
}
- if (note.isHidden) {
- return `(⛔)`;
- }
-
let summary = '';
// 本文
diff --git a/packages/backend/src/models/schema/note.ts b/packages/backend/src/models/schema/note.ts
index cdf4b9a54..292bbb82f 100644
--- a/packages/backend/src/models/schema/note.ts
+++ b/packages/backend/src/models/schema/note.ts
@@ -52,10 +52,6 @@ export const packedNoteSchema = {
optional: true, nullable: true,
ref: 'Note',
},
- isHidden: {
- type: 'boolean',
- optional: true, nullable: false,
- },
visibility: {
type: 'string',
optional: false, nullable: false,
diff --git a/packages/client/src/components/note-detailed.vue b/packages/client/src/components/note-detailed.vue
index e7bbd70a2..072db16e3 100644
--- a/packages/client/src/components/note-detailed.vue
+++ b/packages/client/src/components/note-detailed.vue
@@ -54,7 +54,6 @@
-
({{ i18n.ts.private }})
RN:
diff --git a/packages/client/src/components/note.vue b/packages/client/src/components/note.vue
index e6a35fe41..eaead8e78 100644
--- a/packages/client/src/components/note.vue
+++ b/packages/client/src/components/note.vue
@@ -43,7 +43,6 @@
-
({{ i18n.ts.private }})
RN:
diff --git a/packages/client/src/components/sub-note-content.vue b/packages/client/src/components/sub-note-content.vue
index 25ab883f4..9ee180345 100644
--- a/packages/client/src/components/sub-note-content.vue
+++ b/packages/client/src/components/sub-note-content.vue
@@ -1,7 +1,6 @@
-
({{ i18n.ts.private }})
({{ i18n.ts.deleted }})
diff --git a/packages/client/src/scripts/get-note-summary.ts b/packages/client/src/scripts/get-note-summary.ts
index d57e1c302..348351206 100644
--- a/packages/client/src/scripts/get-note-summary.ts
+++ b/packages/client/src/scripts/get-note-summary.ts
@@ -10,10 +10,6 @@ export const getNoteSummary = (note: misskey.entities.Note): string => {
return `(${i18n.ts.deletedNote})`;
}
- if (note.isHidden) {
- return `(${i18n.ts.invisibleNote})`;
- }
-
let summary = '';
// 本文
From 6775028b1edd320df0dcf8c52a97c57e429583af Mon Sep 17 00:00:00 2001
From: Johann150
Date: Wed, 1 Jun 2022 10:51:11 +0200
Subject: [PATCH 7/9] adjust tests
---
packages/backend/test/api-visibility.ts | 34 ++++++++++++-------------
1 file changed, 17 insertions(+), 17 deletions(-)
diff --git a/packages/backend/test/api-visibility.ts b/packages/backend/test/api-visibility.ts
index b155549f9..cde3cd2d0 100644
--- a/packages/backend/test/api-visibility.ts
+++ b/packages/backend/test/api-visibility.ts
@@ -154,18 +154,18 @@ describe('API visibility', () => {
it('[show] followers-postを非フォロワーが見れない', async(async () => {
const res = await show(fol.id, other);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] followers-postを未認証が見れない', async(async () => {
const res = await show(fol.id, null);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
// specified
it('[show] specified-postを自分が見れる', async(async () => {
const res = await show(spe.id, alice);
- assert.strictEqual(res.body.text, 'x');
+ assert.strictEqual(res.status, 404);
}));
it('[show] specified-postを指定ユーザーが見れる', async(async () => {
@@ -175,17 +175,17 @@ describe('API visibility', () => {
it('[show] specified-postをフォロワーが見れない', async(async () => {
const res = await show(spe.id, follower);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] specified-postを非フォロワーが見れない', async(async () => {
const res = await show(spe.id, other);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] specified-postを未認証が見れない', async(async () => {
const res = await show(spe.id, null);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
//#endregion
@@ -260,12 +260,12 @@ describe('API visibility', () => {
it('[show] followers-replyを非フォロワーが見れない', async(async () => {
const res = await show(folR.id, other);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] followers-replyを未認証が見れない', async(async () => {
const res = await show(folR.id, null);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
// specified
@@ -286,17 +286,17 @@ describe('API visibility', () => {
it('[show] specified-replyをフォロワーが見れない', async(async () => {
const res = await show(speR.id, follower);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] specified-replyを非フォロワーが見れない', async(async () => {
const res = await show(speR.id, other);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] specified-replyを未認証が見れない', async(async () => {
const res = await show(speR.id, null);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
//#endregion
@@ -371,12 +371,12 @@ describe('API visibility', () => {
it('[show] followers-mentionを非フォロワーが見れない', async(async () => {
const res = await show(folM.id, other);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] followers-mentionを未認証が見れない', async(async () => {
const res = await show(folM.id, null);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
// specified
@@ -392,22 +392,22 @@ describe('API visibility', () => {
it('[show] specified-mentionをされた人が指定されてなかったら見れない', async(async () => {
const res = await show(speM.id, target2);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] specified-mentionをフォロワーが見れない', async(async () => {
const res = await show(speM.id, follower);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] specified-mentionを非フォロワーが見れない', async(async () => {
const res = await show(speM.id, other);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
it('[show] specified-mentionを未認証が見れない', async(async () => {
const res = await show(speM.id, null);
- assert.strictEqual(res.body.isHidden, true);
+ assert.strictEqual(res.status, 404);
}));
//#endregion
From b630cd7eacd695bb705e6748c87f38425ec4ed45 Mon Sep 17 00:00:00 2001
From: Johann150
Date: Thu, 9 Jun 2022 16:50:56 +0200
Subject: [PATCH 8/9] refactor: add NoteReactions.packMany
---
.../src/models/repositories/note-reaction.ts | 14 ++++++++++++++
.../src/server/api/endpoints/notes/reactions.ts | 2 +-
.../src/server/api/endpoints/users/reactions.ts | 2 +-
3 files changed, 16 insertions(+), 2 deletions(-)
diff --git a/packages/backend/src/models/repositories/note-reaction.ts b/packages/backend/src/models/repositories/note-reaction.ts
index 4deae51c9..46084a9a1 100644
--- a/packages/backend/src/models/repositories/note-reaction.ts
+++ b/packages/backend/src/models/repositories/note-reaction.ts
@@ -25,8 +25,22 @@ export const NoteReactionRepository = db.getRepository(NoteReaction).extend({
user: await Users.pack(reaction.user ?? reaction.userId, me),
type: convertLegacyReaction(reaction.reaction),
...(opts.withNote ? {
+ // may throw error
note: await Notes.pack(reaction.note ?? reaction.noteId, me),
} : {}),
};
},
+
+ async packMany(
+ src: NoteReaction[],
+ me?: { id: User['id'] } | null | undefined,
+ options?: {
+ withNote: booleam;
+ },
+ ): Promise[]> {
+ const reactions = await Promise.allSettled(src.map(reaction => this.pack(reaction, me, options)));
+
+ // filter out rejected promises, only keep fulfilled values
+ return reactions.flatMap(result => result.status === 'fulfilled' ? [result.value] : []);
+ }
});
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts
index 02a288138..d9388b47f 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts
@@ -75,5 +75,5 @@ export default define(meta, paramDef, async (ps, user) => {
relations: ['user', 'user.avatar', 'user.banner', 'note'],
});
- return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, user)));
+ return await NoteReactions.packMany(reactions, user);
});
diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts
index 9668bd21b..4750dc4f9 100644
--- a/packages/backend/src/server/api/endpoints/users/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/users/reactions.ts
@@ -62,5 +62,5 @@ export default define(meta, paramDef, async (ps, me) => {
.take(ps.limit)
.getMany();
- return await Promise.all(reactions.map(reaction => NoteReactions.pack(reaction, me, { withNote: true })));
+ return await NoteReactions.packMany(reactions, me, { withNote: true });
});
From 3fe351df6d4e21f7748c46adfa6ca165abd030c0 Mon Sep 17 00:00:00 2001
From: Johann150
Date: Fri, 10 Jun 2022 08:06:02 +0200
Subject: [PATCH 9/9] fix: catch errors from packing with detail
Packing with detail can cause an error if the reply or renote
are not visible to the user, even though the original note is
visible to the user.
---
.../src/server/api/endpoints/notes/show.ts | 4 ++
packages/backend/src/server/web/index.ts | 39 ++++++++++++-------
packages/backend/src/services/note/create.ts | 26 ++++++++-----
3 files changed, 44 insertions(+), 25 deletions(-)
diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts
index 0f5d0c942..c9c148747 100644
--- a/packages/backend/src/server/api/endpoints/notes/show.ts
+++ b/packages/backend/src/server/api/endpoints/notes/show.ts
@@ -39,6 +39,10 @@ export default define(meta, paramDef, async (ps, user) => {
});
return await Notes.pack(note, user, {
+ // FIXME: packing with detail may throw an error if the reply or renote is not visible (#8774)
detail: true,
+ }).catch(err => {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
});
});
diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts
index 3e2011587..03ee8d5ba 100644
--- a/packages/backend/src/server/web/index.ts
+++ b/packages/backend/src/server/web/index.ts
@@ -322,23 +322,32 @@ router.get('/notes/:note', async (ctx, next) => {
});
if (note) {
- const _note = await Notes.pack(note);
- const profile = await UserProfiles.findOneByOrFail({ userId: note.userId });
- const meta = await fetchMeta();
- await ctx.render('note', {
- note: _note,
- profile,
- avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: note.userId })),
- // TODO: Let locale changeable by instance setting
- summary: getNoteSummary(_note),
- instanceName: meta.name || 'Misskey',
- icon: meta.iconUrl,
- themeColor: meta.themeColor,
- });
+ try {
+ // FIXME: packing with detail may throw an error if the reply or renote is not visible (#8774)
+ const _note = await Notes.pack(note);
+ const profile = await UserProfiles.findOneByOrFail({ userId: note.userId });
+ const meta = await fetchMeta();
+ await ctx.render('note', {
+ note: _note,
+ profile,
+ avatarUrl: await Users.getAvatarUrl(await Users.findOneByOrFail({ id: note.userId })),
+ // TODO: Let locale changeable by instance setting
+ summary: getNoteSummary(_note),
+ instanceName: meta.name || 'Misskey',
+ icon: meta.iconUrl,
+ themeColor: meta.themeColor,
+ });
- ctx.set('Cache-Control', 'public, max-age=15');
+ ctx.set('Cache-Control', 'public, max-age=15');
- return;
+ return;
+ } catch (err) {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') {
+ // note not visible to user
+ } else {
+ throw err;
+ }
+ }
}
await next();
diff --git a/packages/backend/src/services/note/create.ts b/packages/backend/src/services/note/create.ts
index 61a811d70..0fce38029 100644
--- a/packages/backend/src/services/note/create.ts
+++ b/packages/backend/src/services/note/create.ts
@@ -640,17 +640,23 @@ async function createMentionedEvents(mentionedUsers: MinimumUser[], note: Note,
continue;
}
- const detailPackedNote = await Notes.pack(note, u, {
- detail: true,
- });
-
- publishMainStream(u.id, 'mention', detailPackedNote);
-
- const webhooks = (await getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('mention'));
- for (const webhook of webhooks) {
- webhookDeliver(webhook, 'mention', {
- note: detailPackedNote,
+ // note with "specified" visibility might not be visible to mentioned users
+ try {
+ const detailPackedNote = await Notes.pack(note, u, {
+ detail: true,
});
+
+ publishMainStream(u.id, 'mention', detailPackedNote);
+
+ const webhooks = (await getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('mention'));
+ for (const webhook of webhooks) {
+ webhookDeliver(webhook, 'mention', {
+ note: detailPackedNote,
+ });
+ }
+ } catch (err) {
+ if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') continue;
+ throw err;
}
// Create notification