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_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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue