forked from FoundKeyGang/FoundKey
server: handle note visibility in SQL
This allows to check visibility recursively, which should hopefully solve problems with timelines not showing up properly. Changelog: Changed
This commit is contained in:
parent
73d546372e
commit
8f5952bb7d
3 changed files with 56 additions and 33 deletions
|
@ -0,0 +1,53 @@
|
||||||
|
export class noteVisibilityFunction1662132062000 {
|
||||||
|
name = 'noteVisibilityFunction1662132062000';
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`
|
||||||
|
CREATE OR REPLACE FUNCTION note_visible(note_id varchar, user_id varchar) RETURNS BOOLEAN
|
||||||
|
LANGUAGE SQL
|
||||||
|
STABLE
|
||||||
|
CALLED ON NULL INPUT
|
||||||
|
AS $$
|
||||||
|
SELECT CASE
|
||||||
|
WHEN note_id IS NULL THEN TRUE
|
||||||
|
WHEN NOT EXISTS (SELECT 1 FROM note WHERE id = note_id) THEN FALSE
|
||||||
|
WHEN user_id IS NULL THEN (
|
||||||
|
-- simplified check without logged in user
|
||||||
|
SELECT
|
||||||
|
visibility IN ('public', 'home')
|
||||||
|
-- check reply / renote recursively
|
||||||
|
AND note_visible("replyId", NULL)
|
||||||
|
AND note_visible("renoteId", NULL)
|
||||||
|
FROM note WHERE note.id = note_id
|
||||||
|
) ELSE (
|
||||||
|
SELECT
|
||||||
|
(
|
||||||
|
visibility IN ('public', 'home')
|
||||||
|
OR
|
||||||
|
user_id = "userId"
|
||||||
|
OR
|
||||||
|
user_id = ANY("visibleUserIds")
|
||||||
|
OR
|
||||||
|
user_id = ANY("mentions")
|
||||||
|
OR (
|
||||||
|
visibility = 'followers'
|
||||||
|
AND
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1 FROM following WHERE "followeeId" = "userId" AND "followerId" = user_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
-- check reply / renote recursively
|
||||||
|
AND note_visible("replyId", user_id)
|
||||||
|
AND note_visible("renoteId", user_id)
|
||||||
|
FROM note WHERE note.id = note_id
|
||||||
|
)
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query('DROP FUNCTION note_visible');
|
||||||
|
}
|
||||||
|
}
|
|
@ -77,7 +77,7 @@ async function populateMyReaction(note: Note, meId: User['id'], _hint_?: {
|
||||||
|
|
||||||
export const NoteRepository = db.getRepository(Note).extend({
|
export const NoteRepository = db.getRepository(Note).extend({
|
||||||
async isVisibleForMe(note: Note, meId: User['id'] | null): Promise<boolean> {
|
async isVisibleForMe(note: Note, meId: User['id'] | null): Promise<boolean> {
|
||||||
// This code must always be synchronized with the checks in generateVisibilityQuery.
|
// This code must always be synchronized with the `note_visible` SQL function.
|
||||||
// visibility が specified かつ自分が指定されていなかったら非表示
|
// visibility が specified かつ自分が指定されていなかったら非表示
|
||||||
if (note.visibility === 'specified') {
|
if (note.visibility === 'specified') {
|
||||||
if (meId == null) {
|
if (meId == null) {
|
||||||
|
|
|
@ -3,40 +3,10 @@ import { User } from '@/models/entities/user.js';
|
||||||
import { Followings } from '@/models/index.js';
|
import { Followings } from '@/models/index.js';
|
||||||
|
|
||||||
export function generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: { id: User['id'] } | null) {
|
export function generateVisibilityQuery(q: SelectQueryBuilder<any>, me?: { id: User['id'] } | null) {
|
||||||
// This code must always be synchronized with the checks in Notes.isVisibleForMe.
|
|
||||||
if (me == null) {
|
if (me == null) {
|
||||||
q.andWhere(new Brackets(qb => { qb
|
q.andWhere('note_visible(note.id, null)');
|
||||||
.where("note.visibility = 'public'")
|
|
||||||
.orWhere("note.visibility = 'home'");
|
|
||||||
}));
|
|
||||||
} else {
|
} else {
|
||||||
const followingQuery = Followings.createQueryBuilder('following')
|
q.andWhere('note_visible(note.id, :meId)');
|
||||||
.select('following.followeeId')
|
|
||||||
.where('following.followerId = :meId');
|
|
||||||
|
|
||||||
q.andWhere(new Brackets(qb => { qb
|
|
||||||
// 公開投稿である
|
|
||||||
.where(new Brackets(qb => { qb
|
|
||||||
.where("note.visibility = 'public'")
|
|
||||||
.orWhere("note.visibility = 'home'");
|
|
||||||
}))
|
|
||||||
// または 自分自身
|
|
||||||
.orWhere('note.userId = :meId')
|
|
||||||
// または 自分宛て
|
|
||||||
.orWhere(':meId = ANY(note.visibleUserIds)')
|
|
||||||
.orWhere(':meId = ANY(note.mentions)')
|
|
||||||
.orWhere(new Brackets(qb => { qb
|
|
||||||
// または フォロワー宛ての投稿であり、
|
|
||||||
.where("note.visibility = 'followers'")
|
|
||||||
.andWhere(new Brackets(qb => { qb
|
|
||||||
// 自分がフォロワーである
|
|
||||||
.where(`note.userId IN (${ followingQuery.getQuery() })`)
|
|
||||||
// または 自分の投稿へのリプライ
|
|
||||||
.orWhere('note.replyUserId = :meId');
|
|
||||||
}));
|
|
||||||
}));
|
|
||||||
}));
|
|
||||||
|
|
||||||
q.setParameters({ meId: me.id });
|
q.setParameters({ meId: me.id });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue