From 2c54bc3364654a85d63bc48edc55fb24959535ba Mon Sep 17 00:00:00 2001 From: naskya Date: Fri, 7 Jun 2024 08:39:57 +0900 Subject: [PATCH] remove queries to scheduled_note table --- .../src/service/push_notification.rs | 9 +- .../server/api/mastodon/converters/note.ts | 24 ++-- .../src/server/api/mastodon/helpers/note.ts | 105 ++++++++++++------ .../server/api/mastodon/helpers/timeline.ts | 14 +-- 4 files changed, 88 insertions(+), 64 deletions(-) diff --git a/packages/backend-rs/src/service/push_notification.rs b/packages/backend-rs/src/service/push_notification.rs index 7705c03f2d..aabca4ba2d 100644 --- a/packages/backend-rs/src/service/push_notification.rs +++ b/packages/backend-rs/src/service/push_notification.rs @@ -3,7 +3,10 @@ use crate::{ database::db_conn, misc::get_note_summary::{get_note_summary, PartialNoteToSummarize}, model::entity::{access_token, app, sw_subscription}, - util::{http_client, id::{get_timestamp, InvalidIdError}}, + util::{ + http_client, + id::{get_timestamp, InvalidIdError}, + }, }; use once_cell::sync::OnceCell; use sea_orm::prelude::*; @@ -255,8 +258,8 @@ pub async fn send_push_notification( // TODO: refactoring let mut payload = if use_mastodon_api { - // Content generated per subscription - "".to_string() + // Content generated per subscription + "".to_string() } else { // Format the `content` passed from the TypeScript backend // for Firefish push notifications diff --git a/packages/backend/src/server/api/mastodon/converters/note.ts b/packages/backend/src/server/api/mastodon/converters/note.ts index 55ae6398f4..90d1f95d20 100644 --- a/packages/backend/src/server/api/mastodon/converters/note.ts +++ b/packages/backend/src/server/api/mastodon/converters/note.ts @@ -40,7 +40,6 @@ import type { NoteReaction } from "@/models/entities/note-reaction.js"; import { Cache } from "@/misc/cache.js"; import { isFiltered } from "@/misc/is-filtered.js"; import { unfurl } from "unfurl.js"; -import type { ScheduledNote } from "@/models/entities/scheduled-note.js"; export class NoteConverter { private static noteContentHtmlCache = new Cache( @@ -162,9 +161,7 @@ export class NoteConverter { return renote.url ?? renote.uri ?? `${config.url}/notes/${renote.id}`; }); - const identifier = `${note.id}:${( - note.updatedAt ?? note.createdAt - ).getTime()}`; + const identifier = `${note.id}:${(note.updatedAt ?? note.createdAt).getTime()}`; const text = quoteUri.then((quoteUri) => note.text !== null @@ -579,13 +576,12 @@ export class NoteConverter { /** Encode a schduled note. */ public static async encodeScheduledNote( - scheduledNote: ScheduledNote, - ctx: MastoContext, + note: Note, + _: MastoContext, ): Promise { - const { note, user } = scheduledNote; - const renote = - note.renote ?? (note.renoteId ? getNote(note.renoteId, user) : null); + note.renote ?? + (note.renoteId ? getNote(note.renoteId, { id: note.userId }) : null); const quoteUri = Promise.resolve(renote).then((renote) => { if (!renote || !isQuote(note)) return null; return renote.url ?? renote.uri ?? `${config.url}/notes/${renote.id}`; @@ -604,12 +600,12 @@ export class NoteConverter { const files = DriveFiles.packMany(note.fileIds); const a = await awaitAll({ - id: scheduledNote.noteId, - scheduled_at: scheduledNote.scheduledAt.toISOString(), + id: note.id, + scheduled_at: note.scheduledAt!.toISOString(), params: { text, poll: note.hasPoll - ? populatePoll(note, user?.id ?? null).then((p) => + ? populatePoll(note, note.userId ?? null).then((p) => PollConverter.encodeScheduledPoll(p), ) : null, @@ -622,7 +618,7 @@ export class NoteConverter { in_reply_to_id: note.replyId, language: note.lang, application_id: 0, - idempotency: scheduledNote.id, + idempotency: note.id, with_rate_limit: false, }, media_attachments: files.then((files) => @@ -634,7 +630,7 @@ export class NoteConverter { /** Encode an array of schduled notes. */ public static async encodeManyScheduledNotes( - scheduledNotes: ScheduledNote[], + scheduledNotes: Note[], ctx: MastoContext, ): Promise { const encoded = scheduledNotes.map((n) => this.encodeScheduledNote(n, ctx)); diff --git a/packages/backend/src/server/api/mastodon/helpers/note.ts b/packages/backend/src/server/api/mastodon/helpers/note.ts index 7377347ccf..7539bc7b35 100644 --- a/packages/backend/src/server/api/mastodon/helpers/note.ts +++ b/packages/backend/src/server/api/mastodon/helpers/note.ts @@ -6,7 +6,6 @@ import { NoteFavorites, NoteReactions, Notes, - ScheduledNotes, UserNotePinings, } from "@/models/index.js"; import { generateVisibilityQuery } from "@/server/api/common/generate-visibility-query.js"; @@ -20,7 +19,7 @@ import deleteReaction from "@/services/note/reaction/delete.js"; import createNote, { extractMentionedUsers } from "@/services/note/create.js"; import editNote from "@/services/note/edit.js"; import deleteNote from "@/services/note/delete.js"; -import { genId } from "backend-rs"; +import { fetchMeta, genIdAt } from "backend-rs"; import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js"; import { UserConverter } from "@/server/api/mastodon/converters/user.js"; import { UserHelpers } from "@/server/api/mastodon/helpers/user.js"; @@ -36,15 +35,13 @@ import { MastoApiError } from "@/server/api/mastodon/middleware/catch-errors.js" import { Cache } from "@/misc/cache.js"; import AsyncLock from "async-lock"; import { IdentifiableError } from "@/misc/identifiable-error.js"; -import { IsNull } from "typeorm"; +import { In, IsNull } from "typeorm"; import { getStubMastoContext, type MastoContext, } from "@/server/api/mastodon/index.js"; -import { fetchMeta } from "backend-rs"; import { translate } from "@/misc/translate.js"; import { createScheduledNoteJob } from "@/queue/index.js"; -import type { ScheduledNote } from "@/models/entities/scheduled-note.js"; export class NoteHelpers { public static postIdempotencyCache = new Cache<{ @@ -134,9 +131,11 @@ export class NoteHelpers { }); if (!bookmarked) { + const now = new Date(); + await NoteFavorites.insert({ - id: genId(), - createdAt: new Date(), + id: genIdAt(now), + createdAt: now, noteId: note.id, userId: user.id, }); @@ -385,7 +384,7 @@ export class NoteHelpers { const user = ctx.user as ILocalUser; const files = request.media_ids && request.media_ids.length > 0 - ? DriveFiles.findByIds(request.media_ids) + ? DriveFiles.findBy({ id: In(request.media_ids) }) : []; const reply = request.in_reply_to_id @@ -403,8 +402,10 @@ export class NoteHelpers { const visibility = request.visibility ?? UserHelpers.getDefaultNoteVisibility(ctx); - const data = { - createdAt: new Date(), + const now = new Date(); + + const data = await awaitAll({ + createdAt: now, files: files, poll: request.poll ? { @@ -432,39 +433,71 @@ export class NoteHelpers { ? this.extractMentions(request.text ?? "", ctx) : undefined, ), - }; + }); return createNote( user, - await awaitAll(data), + { + createdAt: now, + scheduledAt: delay != null ? new Date(data.scheduledAt!) : null, + files: data.files, + poll: + data.poll != null + ? { + choices: data.poll.choices, + multiple: data.poll.multiple, + expiresAt: + data.poll.expiresAt != null + ? new Date(data.poll.expiresAt) + : null, + } + : undefined, + text: data.text || undefined, + lang: data.lang, + reply, + renote, + cw: data.cw, + ...(delay != null + ? { + visibility: "specified", + visibleUsers: [], + } + : { + visibility: data.visibility, + visibleUsers: data.visibleUsers, + }), + }, false, - !delay - ? undefined - : async (note) => { - await ScheduledNotes.insert({ - id: genId(), - noteId: note.id, - userId: user.id, - scheduledAt: request.scheduled_at, - }); - + delay + ? async (note) => { createScheduledNoteJob( { user: { id: user.id }, noteId: note.id, option: { - poll: data.poll, - visibility: await visibility, + poll: data.poll + ? { + choices: data.poll.choices, + multiple: data.poll.multiple, + expiresAt: data.poll.expiresAt + ? new Date(data.poll.expiresAt) + : null, + } + : undefined, + visibility: data.visibility, visibleUserIds: await Promise.resolve(visibility) .then((v) => v === "specified" ? data.visibleUsers : undefined, ) .then((users) => users?.map((u) => u.id)), + replyId: data.reply?.id ?? undefined, + renoteId: data.renote?.id ?? undefined, }, }, delay, ); - }, + } + : undefined, ); } @@ -476,7 +509,7 @@ export class NoteHelpers { const user = ctx.user as ILocalUser; const files = request.media_ids && request.media_ids.length > 0 - ? await DriveFiles.findByIds(request.media_ids) + ? await DriveFiles.findBy({ id: In(request.media_ids) }) : []; if (request.media_attributes && request.media_attributes.length > 0) { @@ -750,17 +783,17 @@ export class NoteHelpers { minId: string | undefined, limit = 20, ctx: MastoContext, - ): Promise { + ): Promise { if (limit > 40) limit = 40; const query = PaginationHelpers.makePaginationQuery( - ScheduledNotes.createQueryBuilder("scheduledNote"), + Notes.createQueryBuilder("note"), sinceId, maxId, minId, ) - .leftJoinAndSelect("scheduledNote.note", "note") - .leftJoinAndSelect("scheduledNote.user", "user"); + .andWhere("note.scheduledAt IS NOT NULL") + .leftJoinAndSelect("note.user", "user"); return PaginationHelpers.execQueryLinkPagination( query, @@ -773,13 +806,13 @@ export class NoteHelpers { public static async getScheduledNoteOr404( id: string, ctx: MastoContext, - ): Promise { + ): Promise { const user = ctx.user as ILocalUser | null; - const query = ScheduledNotes.createQueryBuilder("scheduledNote") - .where("scheduledNote.noteId = :id", { id }) - .andWhere("scheduledNote.userId = :userId", { userId: user?.id }) - .leftJoinAndSelect("scheduledNote.note", "note") - .leftJoinAndSelect("scheduledNote.user", "user"); + const query = Notes.createQueryBuilder("note") + .where("note.id = :id", { id }) + .andWhere("note.userId = :userId", { userId: user?.id }) + .andWhere("note.scheduledAt IS NOT NULL") + .leftJoinAndSelect("note.user", "user"); return query.getOneOrFail().catch((_) => { throw new MastoApiError(404); }); diff --git a/packages/backend/src/server/api/mastodon/helpers/timeline.ts b/packages/backend/src/server/api/mastodon/helpers/timeline.ts index f1600cdaa0..c9db284064 100644 --- a/packages/backend/src/server/api/mastodon/helpers/timeline.ts +++ b/packages/backend/src/server/api/mastodon/helpers/timeline.ts @@ -1,7 +1,6 @@ import type { Note } from "@/models/entities/note.js"; import type { ILocalUser, User } from "@/models/entities/user.js"; import { - Followings, Notes, Notifications, RegistryItems, @@ -14,7 +13,7 @@ import { generateVisibilityQuery } from "@/server/api/common/generate-visibility import { generateMutedUserQuery } from "@/server/api/common/generate-muted-user-query.js"; import { generateBlockedUserQuery } from "@/server/api/common/generate-block-query.js"; import { generateMutedUserRenotesQueryForNotes } from "@/server/api/common/generated-muted-renote-query.js"; -import { fetchMeta } from "backend-rs"; +import { fetchMeta, genId } from "backend-rs"; import { PaginationHelpers } from "@/server/api/mastodon/helpers/pagination.js"; import type { UserList } from "@/models/entities/user-list.js"; import { UserHelpers } from "@/server/api/mastodon/helpers/user.js"; @@ -27,7 +26,6 @@ import { generatePaginationData } from "@/server/api/mastodon/middleware/paginat import type { MastoContext } from "@/server/api/mastodon/index.js"; import { generateListQuery } from "@/server/api/common/generate-list-query.js"; import { generateFollowingQuery } from "@/server/api/common/generate-following-query.js"; -import { genId } from "backend-rs"; export class TimelineHelpers { public static async getHomeTimeline( @@ -379,14 +377,8 @@ export class TimelineHelpers { return result; } - /** Exclude scheduled notes from Mastodon timeline (visibility === "specified" && visibleUserIds.length === 0) */ + /** Exclude scheduled notes from Mastodon timeline */ public static generateNoScheduleNotesQuery(q: SelectQueryBuilder) { - q.andWhere( - new Brackets((qb) => { - qb.where("note.visibility != 'specified'").orWhere( - "array_length(note.visibleUserIds, 1) != 0", - ); - }), - ); + q.andWhere("note.scheduledAt IS NULL"); } }