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