store vote to scylla

This commit is contained in:
Namekuji 2023-08-17 23:15:12 -04:00
parent 6d49a39273
commit db573c342c
No known key found for this signature in database
GPG key ID: 1D62332C07FBA532
3 changed files with 110 additions and 25 deletions

View file

@ -3,6 +3,7 @@ import type { Note } from "@/models/entities/note.js";
import type { IRemoteUser, User } from "@/models/entities/user.js";
import type { PollVote } from "@/models/entities/poll-vote.js";
import type { Poll } from "@/models/entities/poll.js";
import { ScyllaNote, ScyllaPollVote } from "@/db/scylla";
export default async function renderVote(
user: { id: User["id"] },
@ -27,3 +28,28 @@ export default async function renderVote(
},
};
}
export async function renderScyllaPollVote(
user: { id: User["id"] },
note: ScyllaNote,
choice: number,
pollOwner: IRemoteUser,
): Promise<any> {
if (!note.poll) throw new Error("note has no poll");
return {
id: `${config.url}/users/${user.id}#votes/${note.id}/activity`,
actor: `${config.url}/users/${user.id}`,
type: "Create",
to: [pollOwner.uri],
published: new Date().toISOString(),
object: {
id: `${config.url}/users/${user.id}#votes/${note.id}`,
type: "Note",
attributedTo: `${config.url}/users/${user.id}`,
to: [pollOwner.uri],
inReplyTo: note.uri,
name: note.poll.choices.get(choice),
},
};
}

View file

@ -3,7 +3,9 @@ import { publishNoteStream } from "@/services/stream.js";
import { createNotification } from "@/services/create-notification.js";
import { deliver } from "@/queue/index.js";
import { renderActivity } from "@/remote/activitypub/renderer/index.js";
import renderVote from "@/remote/activitypub/renderer/vote.js";
import renderVote, {
renderScyllaPollVote,
} from "@/remote/activitypub/renderer/vote.js";
import {
PollVotes,
NoteWatchings,
@ -16,6 +18,9 @@ import { genId } from "@/misc/gen-id.js";
import { getNote } from "../../../common/getters.js";
import { ApiError } from "../../../error.js";
import define from "../../../define.js";
import createVote from "@/services/note/polls/vote.js";
import { type ScyllaNote, scyllaClient } from "@/db/scylla.js";
import { userByIdCache } from "@/services/user-cache.js";
export const meta = {
tags: ["notes"],
@ -74,8 +79,6 @@ export const paramDef = {
} as const;
export default define(meta, paramDef, async (ps, user) => {
const createdAt = new Date();
// Get votee
const note = await getNote(ps.noteId, user).catch((err) => {
if (err.id === "9725d0ce-ba28-4dde-95a7-2cbb2c15de24")
@ -87,6 +90,57 @@ export default define(meta, paramDef, async (ps, user) => {
throw new ApiError(meta.errors.noPoll);
}
if (scyllaClient) {
const scyllaNote = note as ScyllaNote;
if (!scyllaNote.poll) {
throw new ApiError(meta.errors.noPoll);
}
if (scyllaNote.poll.expiresAt && scyllaNote.poll.expiresAt < new Date()) {
throw new ApiError(meta.errors.alreadyExpired);
}
await createVote(user, scyllaNote, ps.choice);
if (scyllaNote.userHost) {
const pollOwner = (await userByIdCache.fetch(scyllaNote.userId, () =>
Users.findOneByOrFail({
id: scyllaNote.userId,
}),
)) as IRemoteUser;
deliver(
user,
renderActivity(
await renderScyllaPollVote(user, scyllaNote, ps.choice, pollOwner),
),
pollOwner.inbox,
);
}
publishNoteStream(scyllaNote.id, "pollVoted", {
choice: ps.choice,
userId: user.id,
});
createNotification(scyllaNote.userId, "pollVote", {
notifierId: user.id,
noteId: scyllaNote.id,
choice: ps.choice,
});
NoteWatchings.findBy({
noteId: scyllaNote.id,
userId: Not(user.id),
}).then((watchers) => {
for (const watcher of watchers) {
createNotification(watcher.userId, "pollVote", {
notifierId: user.id,
noteId: scyllaNote.id,
choice: ps.choice,
});
}
});
return;
}
// Check blocking
if (note.userId !== user.id) {
const block = await Blockings.findOneBy({
@ -100,6 +154,8 @@ export default define(meta, paramDef, async (ps, user) => {
const poll = await Polls.findOneByOrFail({ noteId: note.id });
const createdAt = new Date();
if (poll.expiresAt && poll.expiresAt < createdAt) {
throw new ApiError(meta.errors.alreadyExpired);
}

View file

@ -2,7 +2,6 @@ import { publishMainStream } from "@/services/stream.js";
import { pushNotification } from "@/services/push-notification.js";
import {
Notifications,
Mutings,
NoteThreadMutings,
UserProfiles,
Users,
@ -13,6 +12,8 @@ import type { User } from "@/models/entities/user.js";
import type { Notification } from "@/models/entities/notification.js";
import { sendEmailNotification } from "./send-email-notification.js";
import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
import { UserMutingsCache } from "@/misc/cache.js";
import { userByIdCache } from "./user-cache.js";
export async function createNotification(
notifieeId: User["id"],
@ -47,10 +48,12 @@ export async function createNotification(
const isMuted = profile?.mutingNotificationTypes.includes(type);
if (data.note != null) {
const threadMute = await NoteThreadMutings.findOneBy({
userId: notifieeId,
threadId: data.note.threadId || data.note.id,
if (data.note) {
const threadMute = await NoteThreadMutings.exist({
where: {
userId: notifieeId,
threadId: data.note.threadId || data.note.id,
},
});
if (threadMute) {
@ -64,7 +67,7 @@ export async function createNotification(
createdAt: new Date(),
notifieeId: notifieeId,
type: type,
// 相手がこの通知をミュートしているようなら、既読を予めつけておく
// Make this notification read if muted
isRead: isMuted,
...data,
} as Partial<Notification>).then((x) =>
@ -76,38 +79,38 @@ export async function createNotification(
// Publish notification event
publishMainStream(notifieeId, "notification", packed);
// 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する
// Fire "new notification" event if not yet read after two seconds
setTimeout(async () => {
const fresh = await Notifications.findOneBy({ id: notification.id });
if (fresh == null) return; // 既に削除されているかもしれない
if (!fresh) return;
// We execute this before, because the server side "read" check doesnt work well with push notifications, the app and service worker will decide themself
// when it is best to show push notifications
pushNotification(notifieeId, "notification", packed);
if (fresh.isRead) return;
//#region ただしミュートしているユーザーからの通知なら無視
const mutings = await Mutings.findBy({
muterId: notifieeId,
});
if (
data.notifierId &&
mutings.map((m) => m.muteeId).includes(data.notifierId)
) {
return;
// Ignore if the issuer is muted.
if (data.notifierId) {
const cache = await UserMutingsCache.init(notifieeId);
if (await cache.isMuting(data.notifierId)) {
return;
}
}
//#endregion
publishMainStream(notifieeId, "unreadNotification", packed);
if (type === "follow")
if (type === "follow" && data.notifierId)
sendEmailNotification.follow(
notifieeId,
await Users.findOneByOrFail({ id: data.notifierId! }),
await userByIdCache.fetch(data.notifierId, () =>
Users.findOneByOrFail({ id: data.notifierId }),
),
);
if (type === "receiveFollowRequest")
if (type === "receiveFollowRequest" && data.notifierId)
sendEmailNotification.receiveFollowRequest(
notifieeId,
await Users.findOneByOrFail({ id: data.notifierId! }),
await userByIdCache.fetch(data.notifierId, () =>
Users.findOneByOrFail({ id: data.notifierId }),
),
);
}, 2000);