get files and children in scylla

This commit is contained in:
Namekuji 2023-08-12 23:59:54 -04:00
parent 38ad1cb38b
commit 411cbdc121
No known key found for this signature in database
GPG key ID: 1D62332C07FBA532
8 changed files with 118 additions and 24 deletions

View file

@ -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_and_user_id;
DROP MATERIALIZED VIEW IF EXISTS note_by_renote_id; DROP MATERIALIZED VIEW IF EXISTS note_by_renote_id;
DROP MATERIALIZED VIEW IF EXISTS note_by_user_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_id;
DROP INDEX IF EXISTS note_by_uri; DROP INDEX IF EXISTS note_by_uri;
DROP INDEX IF EXISTS note_by_url; DROP INDEX IF EXISTS note_by_url;

View file

@ -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_uri ON note ("uri");
CREATE INDEX IF NOT EXISTS note_by_url ON note ("url"); 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_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 CREATE MATERIALIZED VIEW IF NOT EXISTS note_by_user_id AS
SELECT * FROM note SELECT * FROM note

View file

@ -48,6 +48,7 @@ export const scyllaQueries = {
byId: `SELECT * FROM note WHERE "id" = ?`, byId: `SELECT * FROM note WHERE "id" = ?`,
byUserId: `SELECT * FROM note_by_user_id WHERE "userId" IN ?`, byUserId: `SELECT * FROM note_by_user_id WHERE "userId" IN ?`,
byRenoteId: `SELECT * FROM note_by_renote_id WHERE "renoteId" = ?`, 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" = ?`, delete: `DELETE FROM note WHERE "createdAtDate" = ? AND "createdAt" = ? AND "userId" = ? AND "userHost" = ? AND "visibility" = ?`,
update: { update: {

View file

@ -82,6 +82,24 @@ export interface ScyllaDriveFile {
height: number | null; 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 { export interface ScyllaNoteEditHistory {
content: string; content: string;
cw: string; cw: string;

View file

@ -10,6 +10,7 @@ import { Meta } from "@/models/entities/meta.js";
import { fetchMeta } from "@/misc/fetch-meta.js"; import { fetchMeta } from "@/misc/fetch-meta.js";
import { Users, DriveFolders } from "../index.js"; import { Users, DriveFolders } from "../index.js";
import { deepClone } from "@/misc/clone.js"; import { deepClone } from "@/misc/clone.js";
import { ScyllaDriveFile } from "@/db/scylla.js";
type PackOptions = { type PackOptions = {
detail?: boolean; detail?: boolean;

View file

@ -33,6 +33,7 @@ import {
prepared, prepared,
scyllaClient, scyllaClient,
parseScyllaReaction, parseScyllaReaction,
getScyllaDrivePublicUrl,
} from "@/db/scylla.js"; } from "@/db/scylla.js";
import { LocalFollowingsCache } from "@/misc/cache.js"; import { LocalFollowingsCache } from "@/misc/cache.js";
import { userByIdCache } from "@/services/user-cache.js"; import { userByIdCache } from "@/services/user-cache.js";
@ -280,6 +281,7 @@ export const NoteRepository = db.getRepository(Note).extend({
files: scyllaClient files: scyllaClient
? (note as ScyllaNote).files.map((file) => ({ ? (note as ScyllaNote).files.map((file) => ({
...file, ...file,
thumbnailUrl: getScyllaDrivePublicUrl(file, true),
createdAt: file.createdAt.toISOString(), createdAt: file.createdAt.toISOString(),
properties: { properties: {
width: file.width ?? undefined, width: file.width ?? undefined,

View file

@ -4,6 +4,9 @@ import { makePaginationQuery } from "../../common/make-pagination-query.js";
import { generateVisibilityQuery } from "../../common/generate-visibility-query.js"; import { generateVisibilityQuery } from "../../common/generate-visibility-query.js";
import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js"; import { generateMutedUserQuery } from "../../common/generate-muted-user-query.js";
import { generateBlockedUserQuery } from "../../common/generate-block-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 = { export const meta = {
tags: ["notes"], tags: ["notes"],
@ -38,18 +41,52 @@ export const paramDef = {
} as const; } as const;
export default define(meta, paramDef, async (ps, user) => { 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( const query = makePaginationQuery(
Notes.createQueryBuilder("note"), Notes.createQueryBuilder("note"),
ps.sinceId, ps.sinceId,
ps.untilId, ps.untilId,
) ).andWhere(
.andWhere(
"note.id IN (SELECT id FROM note_replies(:noteId, :depth, :limit))", "note.id IN (SELECT id FROM note_replies(:noteId, :depth, :limit))",
{ noteId: ps.noteId, depth: ps.depth, limit: ps.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); generateVisibilityQuery(query, user);
if (user) { if (user) {

View file

@ -148,7 +148,7 @@ export default async function (
deletedAt: deletedAt, deletedAt: deletedAt,
}); });
//#region ローカルの投稿なら削除アクティビティを配送 //#region Deliver Delete activity if it's from a local account
if (Users.isLocalUser(user) && !note.localOnly) { if (Users.isLocalUser(user) && !note.localOnly) {
let renote: Note | null = null; let renote: Note | null = null;
@ -159,10 +159,19 @@ export default async function (
!note.hasPoll && !note.hasPoll &&
(note.fileIds == null || note.fileIds.length === 0) (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({ renote = await Notes.findOneBy({
id: note.renoteId, id: note.renoteId,
}); });
} }
}
const content = renderActivity( const content = renderActivity(
renote renote
@ -249,6 +258,28 @@ async function findCascadingNotes(note: Note) {
const cascadingNotes: Note[] = []; const cascadingNotes: Note[] = [];
const recursive = async (noteId: string) => { 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") const query = Notes.createQueryBuilder("note")
.where("note.replyId = :noteId", { noteId }) .where("note.replyId = :noteId", { noteId })
.orWhere( .orWhere(
@ -259,8 +290,10 @@ async function findCascadingNotes(note: Note) {
}), }),
) )
.leftJoinAndSelect("note.user", "user"); .leftJoinAndSelect("note.user", "user");
const replies = await query.getMany(); notes = await query.getMany();
for (const reply of replies) { }
for (const reply of notes) {
cascadingNotes.push(reply); cascadingNotes.push(reply);
await recursive(reply.id); await recursive(reply.id);
} }