get files and children in scylla
This commit is contained in:
parent
38ad1cb38b
commit
411cbdc121
8 changed files with 118 additions and 24 deletions
|
@ -9,6 +9,7 @@ DROP MATERIALIZED VIEW IF EXISTS global_timeline;
|
|||
DROP MATERIALIZED VIEW IF EXISTS note_by_renote_id_and_user_id;
|
||||
DROP MATERIALIZED VIEW IF EXISTS note_by_renote_id;
|
||||
DROP MATERIALIZED VIEW IF EXISTS note_by_user_id;
|
||||
DROP INDEX IF EXISTS note_by_reply_id;
|
||||
DROP INDEX IF EXISTS note_by_id;
|
||||
DROP INDEX IF EXISTS note_by_uri;
|
||||
DROP INDEX IF EXISTS note_by_url;
|
||||
|
|
|
@ -75,6 +75,7 @@ CREATE TABLE IF NOT EXISTS note ( -- Store all posts
|
|||
CREATE INDEX IF NOT EXISTS note_by_uri ON note ("uri");
|
||||
CREATE INDEX IF NOT EXISTS note_by_url ON note ("url");
|
||||
CREATE INDEX IF NOT EXISTS note_by_id ON note ("id");
|
||||
CREATE INDEX IF NOT EXISTS note_by_reply_id ON note ("replyId");
|
||||
|
||||
CREATE MATERIALIZED VIEW IF NOT EXISTS note_by_user_id AS
|
||||
SELECT * FROM note
|
||||
|
|
|
@ -48,6 +48,7 @@ export const scyllaQueries = {
|
|||
byId: `SELECT * FROM note WHERE "id" = ?`,
|
||||
byUserId: `SELECT * FROM note_by_user_id WHERE "userId" IN ?`,
|
||||
byRenoteId: `SELECT * FROM note_by_renote_id WHERE "renoteId" = ?`,
|
||||
byReplyId: `SELECT * FROM note_by_reply_id WHERE "replyId" = ?`
|
||||
},
|
||||
delete: `DELETE FROM note WHERE "createdAtDate" = ? AND "createdAt" = ? AND "userId" = ? AND "userHost" = ? AND "visibility" = ?`,
|
||||
update: {
|
||||
|
|
|
@ -82,6 +82,24 @@ export interface ScyllaDriveFile {
|
|||
height: number | null;
|
||||
}
|
||||
|
||||
export function getScyllaDrivePublicUrl(file: ScyllaDriveFile, thumbnail = false): string | null {
|
||||
const isImage =
|
||||
file.type &&
|
||||
[
|
||||
"image/png",
|
||||
"image/apng",
|
||||
"image/gif",
|
||||
"image/jpeg",
|
||||
"image/webp",
|
||||
"image/svg+xml",
|
||||
"image/avif",
|
||||
].includes(file.type);
|
||||
|
||||
return thumbnail
|
||||
? file.thumbnailUrl || (isImage ? file.url : null)
|
||||
: file.url;
|
||||
}
|
||||
|
||||
export interface ScyllaNoteEditHistory {
|
||||
content: string;
|
||||
cw: string;
|
||||
|
|
|
@ -10,6 +10,7 @@ import { Meta } from "@/models/entities/meta.js";
|
|||
import { fetchMeta } from "@/misc/fetch-meta.js";
|
||||
import { Users, DriveFolders } from "../index.js";
|
||||
import { deepClone } from "@/misc/clone.js";
|
||||
import { ScyllaDriveFile } from "@/db/scylla.js";
|
||||
|
||||
type PackOptions = {
|
||||
detail?: boolean;
|
||||
|
|
|
@ -33,6 +33,7 @@ import {
|
|||
prepared,
|
||||
scyllaClient,
|
||||
parseScyllaReaction,
|
||||
getScyllaDrivePublicUrl,
|
||||
} from "@/db/scylla.js";
|
||||
import { LocalFollowingsCache } from "@/misc/cache.js";
|
||||
import { userByIdCache } from "@/services/user-cache.js";
|
||||
|
@ -280,6 +281,7 @@ export const NoteRepository = db.getRepository(Note).extend({
|
|||
files: scyllaClient
|
||||
? (note as ScyllaNote).files.map((file) => ({
|
||||
...file,
|
||||
thumbnailUrl: getScyllaDrivePublicUrl(file, true),
|
||||
createdAt: file.createdAt.toISOString(),
|
||||
properties: {
|
||||
width: file.width ?? undefined,
|
||||
|
|
|
@ -4,6 +4,9 @@ import { makePaginationQuery } from "../../common/make-pagination-query.js";
|
|||
import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
|
||||
import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
|
||||
import { generateBlockedUserQuery } from "../../common/generate-block-query.js";
|
||||
import { parseScyllaNote, prepared, scyllaClient } from "@/db/scylla.js";
|
||||
import { getNote } from "@/server/api/common/getters.js";
|
||||
import type { Note } from "@/models/entities/note.js";
|
||||
|
||||
export const meta = {
|
||||
tags: ["notes"],
|
||||
|
@ -38,18 +41,52 @@ export const paramDef = {
|
|||
} as const;
|
||||
|
||||
export default define(meta, paramDef, async (ps, user) => {
|
||||
if (scyllaClient) {
|
||||
const root = await getNote(ps.noteId, user).catch(() => null);
|
||||
if (!root) {
|
||||
return await Notes.packMany([]);
|
||||
}
|
||||
|
||||
// Find replies in BFS manner
|
||||
const queue = [root];
|
||||
const foundReplies: Note[] = [];
|
||||
let depth = 0;
|
||||
|
||||
while (
|
||||
queue.length > 0 &&
|
||||
foundReplies.length < ps.limit &&
|
||||
depth < ps.depth
|
||||
) {
|
||||
const note = queue.shift();
|
||||
if (note) {
|
||||
const result = await scyllaClient.execute(
|
||||
prepared.note.select.byReplyId,
|
||||
[note.id],
|
||||
{ prepare: true },
|
||||
);
|
||||
if (result.rowLength > 0) {
|
||||
const replies = result.rows.map(parseScyllaNote);
|
||||
foundReplies.push(...replies);
|
||||
queue.push(...replies);
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return await Notes.packMany(foundReplies.slice(0, ps.limit), user, {
|
||||
detail: false,
|
||||
scyllaNote: true,
|
||||
});
|
||||
}
|
||||
|
||||
const query = makePaginationQuery(
|
||||
Notes.createQueryBuilder("note"),
|
||||
ps.sinceId,
|
||||
ps.untilId,
|
||||
)
|
||||
.andWhere(
|
||||
).andWhere(
|
||||
"note.id IN (SELECT id FROM note_replies(:noteId, :depth, :limit))",
|
||||
{ noteId: ps.noteId, depth: ps.depth, limit: ps.limit },
|
||||
)
|
||||
.innerJoinAndSelect("note.user", "user")
|
||||
.leftJoinAndSelect("user.avatar", "avatar")
|
||||
.leftJoinAndSelect("user.banner", "banner");
|
||||
);
|
||||
|
||||
generateVisibilityQuery(query, user);
|
||||
if (user) {
|
||||
|
|
|
@ -148,7 +148,7 @@ export default async function (
|
|||
deletedAt: deletedAt,
|
||||
});
|
||||
|
||||
//#region ローカルの投稿なら削除アクティビティを配送
|
||||
//#region Deliver Delete activity if it's from a local account
|
||||
if (Users.isLocalUser(user) && !note.localOnly) {
|
||||
let renote: Note | null = null;
|
||||
|
||||
|
@ -159,10 +159,19 @@ export default async function (
|
|||
!note.hasPoll &&
|
||||
(note.fileIds == null || note.fileIds.length === 0)
|
||||
) {
|
||||
if (scyllaClient) {
|
||||
const result = await scyllaClient.execute(
|
||||
prepared.note.select.byId,
|
||||
[note.renoteId],
|
||||
{ prepare: true },
|
||||
);
|
||||
if (result.rowLength > 0) renote = parseScyllaNote(result.first());
|
||||
} else {
|
||||
renote = await Notes.findOneBy({
|
||||
id: note.renoteId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const content = renderActivity(
|
||||
renote
|
||||
|
@ -249,6 +258,28 @@ async function findCascadingNotes(note: Note) {
|
|||
const cascadingNotes: Note[] = [];
|
||||
|
||||
const recursive = async (noteId: string) => {
|
||||
let notes: Note[] = [];
|
||||
|
||||
if (scyllaClient) {
|
||||
const replies = await scyllaClient.execute(
|
||||
prepared.note.select.byReplyId,
|
||||
[noteId],
|
||||
{ prepare: true },
|
||||
);
|
||||
if (replies.rowLength > 0) {
|
||||
notes.push(...replies.rows.map(parseScyllaNote));
|
||||
}
|
||||
const renotes = await scyllaClient.execute(
|
||||
prepared.note.select.byRenoteId,
|
||||
[noteId],
|
||||
{ prepare: true },
|
||||
);
|
||||
if (renotes.rowLength > 0) {
|
||||
notes.push(
|
||||
...renotes.rows.map(parseScyllaNote).filter((note) => !!note.text),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const query = Notes.createQueryBuilder("note")
|
||||
.where("note.replyId = :noteId", { noteId })
|
||||
.orWhere(
|
||||
|
@ -259,8 +290,10 @@ async function findCascadingNotes(note: Note) {
|
|||
}),
|
||||
)
|
||||
.leftJoinAndSelect("note.user", "user");
|
||||
const replies = await query.getMany();
|
||||
for (const reply of replies) {
|
||||
notes = await query.getMany();
|
||||
}
|
||||
|
||||
for (const reply of notes) {
|
||||
cascadingNotes.push(reply);
|
||||
await recursive(reply.id);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue