note edit endpoint
This commit is contained in:
parent
ef97630460
commit
697361ca03
5 changed files with 455 additions and 154 deletions
|
@ -162,6 +162,7 @@ export const scyllaQueries = {
|
||||||
poll: {
|
poll: {
|
||||||
select: `SELECT * FROM poll_vote WHERE "noteId" = ?`,
|
select: `SELECT * FROM poll_vote WHERE "noteId" = ?`,
|
||||||
insert: `INSERT INTO poll_vote ("noteId", "userId", "userHost", "choice", "createdAt") VALUES (?, ?, ?, ?, ?)`,
|
insert: `INSERT INTO poll_vote ("noteId", "userId", "userHost", "choice", "createdAt") VALUES (?, ?, ?, ?, ?)`,
|
||||||
|
delete: `DELETE FROM poll_vote WHERE "noteId" = ?`
|
||||||
},
|
},
|
||||||
notification: {
|
notification: {
|
||||||
insert: `INSERT INTO notification
|
insert: `INSERT INTO notification
|
||||||
|
|
|
@ -109,7 +109,7 @@ export interface ScyllaDriveFile {
|
||||||
comment: string | null;
|
comment: string | null;
|
||||||
blurhash: string | null;
|
blurhash: string | null;
|
||||||
url: string;
|
url: string;
|
||||||
thumbnailUrl: string;
|
thumbnailUrl: string | null;
|
||||||
isSensitive: boolean;
|
isSensitive: boolean;
|
||||||
isLink: boolean;
|
isLink: boolean;
|
||||||
md5: string;
|
md5: string;
|
||||||
|
@ -140,8 +140,8 @@ export function getScyllaDrivePublicUrl(
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScyllaNoteEditHistory {
|
export interface ScyllaNoteEditHistory {
|
||||||
content: string;
|
content: string | null;
|
||||||
cw: string;
|
cw: string | null;
|
||||||
files: ScyllaDriveFile[];
|
files: ScyllaDriveFile[];
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default async (
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isActor(object)) {
|
if (isActor(object)) {
|
||||||
await updatePerson(actor.uri!, resolver, object);
|
await updatePerson(actor.uri as string, resolver, object);
|
||||||
return "ok: Person updated";
|
return "ok: Person updated";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,12 +37,13 @@ export default async (
|
||||||
case "Note":
|
case "Note":
|
||||||
case "Article":
|
case "Article":
|
||||||
case "Document":
|
case "Document":
|
||||||
case "Page":
|
case "Page": {
|
||||||
let failed = false;
|
let failed = false;
|
||||||
await updateNote(object, resolver).catch((e: Error) => {
|
await updateNote(object, resolver).catch((e: Error) => {
|
||||||
failed = true;
|
failed = true;
|
||||||
});
|
});
|
||||||
return failed ? "skip: Note update failed" : "ok: Note updated";
|
return failed ? "skip: Note update failed" : "ok: Note updated";
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return `skip: Unknown type: ${objectType}`;
|
return `skip: Unknown type: ${objectType}`;
|
||||||
|
|
|
@ -53,7 +53,17 @@ import { DB_MAX_IMAGE_COMMENT_LENGTH } from "@/misc/hard-limits.js";
|
||||||
import { truncate } from "@/misc/truncate.js";
|
import { truncate } from "@/misc/truncate.js";
|
||||||
import { type Size, getEmojiSize } from "@/misc/emoji-meta.js";
|
import { type Size, getEmojiSize } from "@/misc/emoji-meta.js";
|
||||||
import { fetchMeta } from "@/misc/fetch-meta.js";
|
import { fetchMeta } from "@/misc/fetch-meta.js";
|
||||||
import { parseScyllaNote, prepared, scyllaClient } from "@/db/scylla.js";
|
import {
|
||||||
|
type ScyllaNote,
|
||||||
|
type ScyllaPoll,
|
||||||
|
type ScyllaDriveFile,
|
||||||
|
type ScyllaNoteEditHistory,
|
||||||
|
parseScyllaNote,
|
||||||
|
prepared,
|
||||||
|
scyllaClient,
|
||||||
|
parseHomeTimeline,
|
||||||
|
} from "@/db/scylla.js";
|
||||||
|
import type { Client } from "cassandra-driver";
|
||||||
|
|
||||||
const logger = apLogger;
|
const logger = apLogger;
|
||||||
|
|
||||||
|
@ -573,20 +583,33 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
|
||||||
if (uri.startsWith(`${config.url}/`)) throw new Error("uri points local");
|
if (uri.startsWith(`${config.url}/`)) throw new Error("uri points local");
|
||||||
|
|
||||||
// A new resolver is created if not specified
|
// A new resolver is created if not specified
|
||||||
if (resolver == null) resolver = new Resolver();
|
let _resolver = resolver;
|
||||||
|
if (!_resolver) _resolver = new Resolver();
|
||||||
|
|
||||||
// Resolve the updated Note object
|
// Resolve the updated Note object
|
||||||
const post = (await resolver.resolve(value)) as IPost;
|
const post = (await _resolver.resolve(value)) as IPost;
|
||||||
|
|
||||||
const actor = (await resolvePerson(
|
const actor = (await resolvePerson(
|
||||||
getOneApId(post.attributedTo),
|
getOneApId(post.attributedTo),
|
||||||
resolver,
|
_resolver,
|
||||||
)) as CacheableRemoteUser;
|
)) as CacheableRemoteUser;
|
||||||
|
|
||||||
// Already registered with this server?
|
// Already registered with this server?
|
||||||
const note = await Notes.findOneBy({ uri });
|
let note: Note | ScyllaNote | null = null;
|
||||||
if (note == null) {
|
if (scyllaClient) {
|
||||||
return await createNote(post, resolver);
|
const result = await scyllaClient.execute(
|
||||||
|
prepared.note.select.byUri,
|
||||||
|
[uri],
|
||||||
|
{ prepare: true },
|
||||||
|
);
|
||||||
|
if (result.rowLength > 0) {
|
||||||
|
note = parseScyllaNote(result.first());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
note = await Notes.findOneBy({ uri });
|
||||||
|
}
|
||||||
|
if (!note) {
|
||||||
|
return await createNote(post, _resolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whether to tell clients the note has been updated and requires refresh.
|
// Whether to tell clients the note has been updated and requires refresh.
|
||||||
|
@ -613,7 +636,9 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
|
||||||
? post.attachment
|
? post.attachment
|
||||||
: [post.attachment]
|
: [post.attachment]
|
||||||
: [];
|
: [];
|
||||||
const files = fileList.map((f) => (f.sensitive = post.sensitive));
|
const files = fileList.map((f) => {
|
||||||
|
f.sensitive = post.sensitive;
|
||||||
|
});
|
||||||
|
|
||||||
// Fetch files
|
// Fetch files
|
||||||
const limit = promiseLimit(2);
|
const limit = promiseLimit(2);
|
||||||
|
@ -654,9 +679,9 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
|
||||||
await extractEmojis(post.tag || [], actor.host).catch((e) => [])
|
await extractEmojis(post.tag || [], actor.host).catch((e) => [])
|
||||||
).map((emoji) => emoji.name);
|
).map((emoji) => emoji.name);
|
||||||
const apMentions = await extractApMentions(post.tag);
|
const apMentions = await extractApMentions(post.tag);
|
||||||
const apHashtags = await extractApHashtags(post.tag);
|
const apHashtags = extractApHashtags(post.tag);
|
||||||
|
|
||||||
const poll = await extractPollFromQuestion(post, resolver).catch(
|
const poll = await extractPollFromQuestion(post, _resolver).catch(
|
||||||
() => undefined,
|
() => undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -719,7 +744,34 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
|
||||||
update.hasPoll = !!poll;
|
update.hasPoll = !!poll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let scyllaPoll: ScyllaPoll | null = null;
|
||||||
|
let scyllaUpdating = false;
|
||||||
|
|
||||||
if (poll) {
|
if (poll) {
|
||||||
|
if (scyllaClient) {
|
||||||
|
let expiresAt: Date | null;
|
||||||
|
if (!poll.expiresAt || isNaN(poll.expiresAt.getTime())) {
|
||||||
|
expiresAt = null;
|
||||||
|
} else {
|
||||||
|
expiresAt = poll.expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
scyllaPoll = {
|
||||||
|
expiresAt,
|
||||||
|
choices: Object.fromEntries(
|
||||||
|
poll.choices.map((v, i) => [i, v] as [number, string]),
|
||||||
|
),
|
||||||
|
multiple: poll.multiple,
|
||||||
|
};
|
||||||
|
|
||||||
|
scyllaUpdating = true;
|
||||||
|
publishing = true;
|
||||||
|
|
||||||
|
// Delete all votes cast to the target note (i.e., delete whole rows in the partition)
|
||||||
|
await scyllaClient.execute(prepared.poll.delete, [note.id], {
|
||||||
|
prepare: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
const dbPoll = await Polls.findOneBy({ noteId: note.id });
|
const dbPoll = await Polls.findOneBy({ noteId: note.id });
|
||||||
if (dbPoll == null) {
|
if (dbPoll == null) {
|
||||||
await Polls.insert({
|
await Polls.insert({
|
||||||
|
@ -728,11 +780,11 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
|
||||||
multiple: poll?.multiple,
|
multiple: poll?.multiple,
|
||||||
votes: poll?.votes,
|
votes: poll?.votes,
|
||||||
expiresAt: poll?.expiresAt,
|
expiresAt: poll?.expiresAt,
|
||||||
noteVisibility: note.visibility === "hidden" ? "home" : note.visibility,
|
noteVisibility:
|
||||||
|
note.visibility === "hidden" ? "home" : note.visibility,
|
||||||
userId: actor.id,
|
userId: actor.id,
|
||||||
userHost: actor.host,
|
userHost: actor.host,
|
||||||
});
|
});
|
||||||
updating = true;
|
|
||||||
} else if (
|
} else if (
|
||||||
dbPoll.multiple !== poll.multiple ||
|
dbPoll.multiple !== poll.multiple ||
|
||||||
dbPoll.expiresAt !== poll.expiresAt ||
|
dbPoll.expiresAt !== poll.expiresAt ||
|
||||||
|
@ -750,7 +802,6 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
|
||||||
note.visibility === "hidden" ? "home" : note.visibility,
|
note.visibility === "hidden" ? "home" : note.visibility,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
updating = true;
|
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < poll.choices.length; i++) {
|
for (let i = 0; i < poll.choices.length; i++) {
|
||||||
if (dbPoll.votes[i] !== poll.votes?.[i]) {
|
if (dbPoll.votes[i] !== poll.votes?.[i]) {
|
||||||
|
@ -761,11 +812,98 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update Note
|
// Update Note
|
||||||
if (notEmpty(update)) {
|
if (notEmpty(update) || scyllaUpdating) {
|
||||||
update.updatedAt = new Date();
|
update.updatedAt = new Date();
|
||||||
|
|
||||||
|
if (scyllaClient) {
|
||||||
|
const client = scyllaClient as Client;
|
||||||
|
const fileMapper = (file: DriveFile) => ({
|
||||||
|
...file,
|
||||||
|
width: file.properties.width ?? null,
|
||||||
|
height: file.properties.height ?? null,
|
||||||
|
});
|
||||||
|
const scyllaFiles: ScyllaDriveFile[] = driveFiles.map(fileMapper);
|
||||||
|
const scyllaNote = note as ScyllaNote;
|
||||||
|
const editHistory: ScyllaNoteEditHistory = {
|
||||||
|
content: scyllaNote.text,
|
||||||
|
cw: scyllaNote.cw,
|
||||||
|
files: scyllaNote.files,
|
||||||
|
updatedAt: update.updatedAt,
|
||||||
|
};
|
||||||
|
|
||||||
|
const newScyllaNote: ScyllaNote = {
|
||||||
|
...scyllaNote,
|
||||||
|
...update,
|
||||||
|
hasPoll: !!scyllaPoll,
|
||||||
|
poll: scyllaPoll,
|
||||||
|
files: scyllaFiles,
|
||||||
|
noteEdit: [...scyllaNote.noteEdit, editHistory],
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = [
|
||||||
|
newScyllaNote.createdAt,
|
||||||
|
newScyllaNote.createdAt,
|
||||||
|
newScyllaNote.id,
|
||||||
|
scyllaNote.visibility,
|
||||||
|
newScyllaNote.text,
|
||||||
|
newScyllaNote.name,
|
||||||
|
newScyllaNote.cw,
|
||||||
|
newScyllaNote.localOnly,
|
||||||
|
newScyllaNote.renoteCount ?? 0,
|
||||||
|
newScyllaNote.repliesCount ?? 0,
|
||||||
|
newScyllaNote.uri,
|
||||||
|
newScyllaNote.url,
|
||||||
|
newScyllaNote.score ?? 0,
|
||||||
|
newScyllaNote.files,
|
||||||
|
newScyllaNote.visibleUserIds,
|
||||||
|
newScyllaNote.mentions,
|
||||||
|
newScyllaNote.mentionedRemoteUsers,
|
||||||
|
newScyllaNote.emojis,
|
||||||
|
newScyllaNote.tags,
|
||||||
|
newScyllaNote.hasPoll,
|
||||||
|
newScyllaNote.poll,
|
||||||
|
newScyllaNote.threadId,
|
||||||
|
newScyllaNote.channelId,
|
||||||
|
newScyllaNote.userId,
|
||||||
|
newScyllaNote.userHost ?? "local",
|
||||||
|
newScyllaNote.replyId,
|
||||||
|
newScyllaNote.replyUserId,
|
||||||
|
newScyllaNote.replyUserHost,
|
||||||
|
newScyllaNote.replyText,
|
||||||
|
newScyllaNote.replyCw,
|
||||||
|
newScyllaNote.replyFiles,
|
||||||
|
newScyllaNote.renoteId,
|
||||||
|
newScyllaNote.renoteUserId,
|
||||||
|
newScyllaNote.renoteUserHost,
|
||||||
|
newScyllaNote.renoteText,
|
||||||
|
newScyllaNote.renoteCw,
|
||||||
|
newScyllaNote.renoteFiles,
|
||||||
|
newScyllaNote.reactions,
|
||||||
|
newScyllaNote.noteEdit,
|
||||||
|
newScyllaNote.updatedAt,
|
||||||
|
];
|
||||||
|
|
||||||
|
// To let ScyllaDB do upsert, do NOT change the visibility.
|
||||||
|
await client.execute(prepared.note.insert, params, { prepare: true });
|
||||||
|
|
||||||
|
// Update home timelines
|
||||||
|
client.eachRow(
|
||||||
|
prepared.homeTimeline.select.byId,
|
||||||
|
[scyllaNote.id],
|
||||||
|
{ prepare: true },
|
||||||
|
(_, row) => {
|
||||||
|
const timeline = parseHomeTimeline(row);
|
||||||
|
client.execute(
|
||||||
|
prepared.homeTimeline.insert,
|
||||||
|
[timeline.feedUserId, ...params],
|
||||||
|
{ prepare: true },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
// Save updated note to the database
|
// Save updated note to the database
|
||||||
await Notes.update({ uri }, update);
|
await Notes.update({ uri }, update);
|
||||||
|
|
||||||
|
@ -778,6 +916,7 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
|
||||||
fileIds: note.fileIds,
|
fileIds: note.fileIds,
|
||||||
updatedAt: update.updatedAt,
|
updatedAt: update.updatedAt,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
publishing = true;
|
publishing = true;
|
||||||
}
|
}
|
||||||
|
@ -788,4 +927,6 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
|
||||||
updatedAt: update.updatedAt,
|
updatedAt: update.updatedAt,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return { ...note, ...update };
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,17 @@ import renderUpdate from "@/remote/activitypub/renderer/update.js";
|
||||||
import { deliverToRelays } from "@/services/relay.js";
|
import { deliverToRelays } from "@/services/relay.js";
|
||||||
// import { deliverQuestionUpdate } from "@/services/note/polls/update.js";
|
// import { deliverQuestionUpdate } from "@/services/note/polls/update.js";
|
||||||
import { fetchMeta } from "@/misc/fetch-meta.js";
|
import { fetchMeta } from "@/misc/fetch-meta.js";
|
||||||
|
import {
|
||||||
|
type ScyllaNote,
|
||||||
|
type ScyllaPoll,
|
||||||
|
type ScyllaDriveFile,
|
||||||
|
type ScyllaNoteEditHistory,
|
||||||
|
scyllaClient,
|
||||||
|
prepared,
|
||||||
|
parseScyllaNote,
|
||||||
|
parseHomeTimeline,
|
||||||
|
} from "@/db/scylla.js";
|
||||||
|
import type { Client } from "cassandra-driver";
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ["notes"],
|
tags: ["notes"],
|
||||||
|
@ -248,7 +259,7 @@ export const paramDef = {
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default define(meta, paramDef, async (ps, user) => {
|
export default define(meta, paramDef, async (ps, user) => {
|
||||||
if (user.movedToUri != null) throw new ApiError(meta.errors.accountLocked);
|
if (user.movedToUri) throw new ApiError(meta.errors.accountLocked);
|
||||||
|
|
||||||
if (!Users.isLocalUser(user)) {
|
if (!Users.isLocalUser(user)) {
|
||||||
throw new ApiError(meta.errors.notLocalUser);
|
throw new ApiError(meta.errors.notLocalUser);
|
||||||
|
@ -259,11 +270,23 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
let publishing = false;
|
let publishing = false;
|
||||||
let note = await Notes.findOneBy({
|
let note: Note | ScyllaNote | null = null;
|
||||||
|
if (scyllaClient) {
|
||||||
|
const result = await scyllaClient.execute(
|
||||||
|
prepared.note.select.byId,
|
||||||
|
[ps.editId],
|
||||||
|
{ prepare: true },
|
||||||
|
);
|
||||||
|
if (result.rowLength > 0) {
|
||||||
|
note = parseScyllaNote(result.first());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
note = await Notes.findOneBy({
|
||||||
id: ps.editId,
|
id: ps.editId,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (note == null) {
|
if (!note) {
|
||||||
throw new ApiError(meta.errors.noSuchNote);
|
throw new ApiError(meta.errors.noSuchNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,7 +295,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
let renote: Note | null = null;
|
let renote: Note | null = null;
|
||||||
if (ps.renoteId != null) {
|
if (ps.renoteId) {
|
||||||
// Fetch renote to note
|
// Fetch renote to note
|
||||||
renote = await getNote(ps.renoteId, user).catch((e) => {
|
renote = await getNote(ps.renoteId, user).catch((e) => {
|
||||||
if (e.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24")
|
if (e.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24")
|
||||||
|
@ -301,7 +324,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
let reply: Note | null = null;
|
let reply: Note | null = null;
|
||||||
if (ps.replyId != null) {
|
if (ps.replyId) {
|
||||||
// Fetch reply
|
// Fetch reply
|
||||||
reply = await getNote(ps.replyId, user).catch((e) => {
|
reply = await getNote(ps.replyId, user).catch((e) => {
|
||||||
if (e.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24")
|
if (e.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24")
|
||||||
|
@ -326,10 +349,10 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
let channel: Channel | null = null;
|
let channel: Channel | null = null;
|
||||||
if (ps.channelId != null) {
|
if (ps.channelId) {
|
||||||
channel = await Channels.findOneBy({ id: ps.channelId });
|
channel = await Channels.findOneBy({ id: ps.channelId });
|
||||||
|
|
||||||
if (channel == null) {
|
if (!channel) {
|
||||||
throw new ApiError(meta.errors.noSuchChannel);
|
throw new ApiError(meta.errors.noSuchChannel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -443,6 +466,9 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
.getMany();
|
.getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let scyllaPoll: ScyllaPoll | null = null;
|
||||||
|
let scyllaUpdating = false;
|
||||||
|
|
||||||
if (ps.poll) {
|
if (ps.poll) {
|
||||||
let expires = ps.poll.expiresAt;
|
let expires = ps.poll.expiresAt;
|
||||||
if (typeof expires === "number") {
|
if (typeof expires === "number") {
|
||||||
|
@ -453,6 +479,32 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
expires = Date.now() + ps.poll.expiredAfter;
|
expires = Date.now() + ps.poll.expiredAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (scyllaClient) {
|
||||||
|
let expiresAt: Date | null;
|
||||||
|
if (!expires || isNaN(expires)) {
|
||||||
|
expiresAt = null;
|
||||||
|
} else {
|
||||||
|
expiresAt = new Date(expires);
|
||||||
|
}
|
||||||
|
|
||||||
|
scyllaPoll = {
|
||||||
|
expiresAt,
|
||||||
|
choices: Object.fromEntries(
|
||||||
|
ps.poll.choices.map((v, i) => [i, v] as [number, string]),
|
||||||
|
),
|
||||||
|
multiple: ps.poll.multiple,
|
||||||
|
};
|
||||||
|
|
||||||
|
publishing = true;
|
||||||
|
scyllaUpdating = true;
|
||||||
|
|
||||||
|
// FIXME: Keep votes for unmodified choices, reset votes if choice is modified or new
|
||||||
|
|
||||||
|
// Delete all votes cast to the target note (i.e., delete whole rows in the partition)
|
||||||
|
await scyllaClient.execute(prepared.poll.delete, [note.id], {
|
||||||
|
prepare: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
let poll = await Polls.findOneBy({ noteId: note.id });
|
let poll = await Polls.findOneBy({ noteId: note.id });
|
||||||
const pp = ps.poll;
|
const pp = ps.poll;
|
||||||
if (!poll && pp) {
|
if (!poll && pp) {
|
||||||
|
@ -494,12 +546,13 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
pollUpdate.votes = newVotes;
|
pollUpdate.votes = newVotes;
|
||||||
if (notEmpty(pollUpdate)) {
|
if (notEmpty(pollUpdate)) {
|
||||||
await Polls.update(note.id, pollUpdate);
|
await Polls.update(note.id, pollUpdate);
|
||||||
// Seemingly already handled by by the rendered update activity
|
// Seemingly already handled by the rendered update activity
|
||||||
// await deliverQuestionUpdate(note.id);
|
// await deliverQuestionUpdate(note.id);
|
||||||
}
|
}
|
||||||
publishing = true;
|
publishing = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const mentionedUserLookup: Record<string, User> = {};
|
const mentionedUserLookup: Record<string, User> = {};
|
||||||
mentionedUsers.forEach((u) => {
|
mentionedUsers.forEach((u) => {
|
||||||
|
@ -542,6 +595,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
// update.visibility = ps.visibility;
|
// update.visibility = ps.visibility;
|
||||||
throw new ApiError(meta.errors.cannotChangeVisibility);
|
throw new ApiError(meta.errors.cannotChangeVisibility);
|
||||||
}
|
}
|
||||||
|
update.localOnly = note.localOnly;
|
||||||
if (ps.localOnly !== note.localOnly) {
|
if (ps.localOnly !== note.localOnly) {
|
||||||
update.localOnly = ps.localOnly;
|
update.localOnly = ps.localOnly;
|
||||||
}
|
}
|
||||||
|
@ -586,8 +640,95 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notEmpty(update)) {
|
if (notEmpty(update) || scyllaUpdating) {
|
||||||
update.updatedAt = new Date();
|
update.updatedAt = new Date();
|
||||||
|
|
||||||
|
if (scyllaClient) {
|
||||||
|
const client = scyllaClient as Client;
|
||||||
|
const fileMapper = (file: DriveFile) => ({
|
||||||
|
...file,
|
||||||
|
width: file.properties.width ?? null,
|
||||||
|
height: file.properties.height ?? null,
|
||||||
|
});
|
||||||
|
const scyllaFiles: ScyllaDriveFile[] = files.map(fileMapper);
|
||||||
|
const scyllaNote = note as ScyllaNote;
|
||||||
|
const editHistory: ScyllaNoteEditHistory = {
|
||||||
|
content: scyllaNote.text,
|
||||||
|
cw: scyllaNote.cw,
|
||||||
|
files: scyllaNote.files,
|
||||||
|
updatedAt: update.updatedAt,
|
||||||
|
};
|
||||||
|
|
||||||
|
const newScyllaNote: ScyllaNote = {
|
||||||
|
...scyllaNote,
|
||||||
|
...update,
|
||||||
|
hasPoll: !!scyllaPoll,
|
||||||
|
poll: scyllaPoll,
|
||||||
|
files: scyllaFiles,
|
||||||
|
noteEdit: [...scyllaNote.noteEdit, editHistory],
|
||||||
|
};
|
||||||
|
|
||||||
|
const params = [
|
||||||
|
newScyllaNote.createdAt,
|
||||||
|
newScyllaNote.createdAt,
|
||||||
|
newScyllaNote.id,
|
||||||
|
scyllaNote.visibility,
|
||||||
|
newScyllaNote.text,
|
||||||
|
newScyllaNote.name,
|
||||||
|
newScyllaNote.cw,
|
||||||
|
newScyllaNote.localOnly,
|
||||||
|
newScyllaNote.renoteCount ?? 0,
|
||||||
|
newScyllaNote.repliesCount ?? 0,
|
||||||
|
newScyllaNote.uri,
|
||||||
|
newScyllaNote.url,
|
||||||
|
newScyllaNote.score ?? 0,
|
||||||
|
newScyllaNote.files,
|
||||||
|
newScyllaNote.visibleUserIds,
|
||||||
|
newScyllaNote.mentions,
|
||||||
|
newScyllaNote.mentionedRemoteUsers,
|
||||||
|
newScyllaNote.emojis,
|
||||||
|
newScyllaNote.tags,
|
||||||
|
newScyllaNote.hasPoll,
|
||||||
|
newScyllaNote.poll,
|
||||||
|
newScyllaNote.threadId,
|
||||||
|
newScyllaNote.channelId,
|
||||||
|
newScyllaNote.userId,
|
||||||
|
newScyllaNote.userHost ?? "local",
|
||||||
|
newScyllaNote.replyId,
|
||||||
|
newScyllaNote.replyUserId,
|
||||||
|
newScyllaNote.replyUserHost,
|
||||||
|
newScyllaNote.replyText,
|
||||||
|
newScyllaNote.replyCw,
|
||||||
|
newScyllaNote.replyFiles,
|
||||||
|
newScyllaNote.renoteId,
|
||||||
|
newScyllaNote.renoteUserId,
|
||||||
|
newScyllaNote.renoteUserHost,
|
||||||
|
newScyllaNote.renoteText,
|
||||||
|
newScyllaNote.renoteCw,
|
||||||
|
newScyllaNote.renoteFiles,
|
||||||
|
newScyllaNote.reactions,
|
||||||
|
newScyllaNote.noteEdit,
|
||||||
|
newScyllaNote.updatedAt,
|
||||||
|
];
|
||||||
|
|
||||||
|
// To let ScyllaDB do upsert, do NOT change the visibility.
|
||||||
|
await client.execute(prepared.note.insert, params, { prepare: true });
|
||||||
|
|
||||||
|
// Update home timelines
|
||||||
|
client.eachRow(
|
||||||
|
prepared.homeTimeline.select.byId,
|
||||||
|
[scyllaNote.id],
|
||||||
|
{ prepare: true },
|
||||||
|
(_, row) => {
|
||||||
|
const timeline = parseHomeTimeline(row);
|
||||||
|
client.execute(
|
||||||
|
prepared.homeTimeline.insert,
|
||||||
|
[timeline.feedUserId, ...params],
|
||||||
|
{ prepare: true },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
await Notes.update(note.id, update);
|
await Notes.update(note.id, update);
|
||||||
|
|
||||||
// Add NoteEdit history
|
// Add NoteEdit history
|
||||||
|
@ -599,11 +740,26 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
fileIds: ps.fileIds,
|
fileIds: ps.fileIds,
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
publishing = true;
|
publishing = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
note = await Notes.findOneBy({ id: note.id });
|
if (scyllaClient) {
|
||||||
|
const result = await scyllaClient.execute(
|
||||||
|
prepared.note.select.byId,
|
||||||
|
[note.id],
|
||||||
|
{ prepare: true },
|
||||||
|
);
|
||||||
|
if (result.rowLength > 0) {
|
||||||
|
note = parseScyllaNote(result.first());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
note = await Notes.findOneBy({
|
||||||
|
id: note.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!note) {
|
if (!note) {
|
||||||
throw new ApiError(meta.errors.noSuchNote);
|
throw new ApiError(meta.errors.noSuchNote);
|
||||||
}
|
}
|
||||||
|
@ -616,6 +772,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
updatedAt: update.updatedAt,
|
updatedAt: update.updatedAt,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!update.localOnly) {
|
||||||
(async () => {
|
(async () => {
|
||||||
const noteActivity = await renderNote(note, false);
|
const noteActivity = await renderNote(note, false);
|
||||||
noteActivity.updated = note.updatedAt.toISOString();
|
noteActivity.updated = note.updatedAt.toISOString();
|
||||||
|
@ -656,6 +813,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
dm.execute();
|
dm.execute();
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createdNote: await Notes.pack(note, user),
|
createdNote: await Notes.pack(note, user),
|
||||||
|
|
Loading…
Reference in a new issue