Merge branch 'develop' into refactor/push-notification

This commit is contained in:
naskya 2024-04-24 04:22:22 +09:00
commit d8a6631f16
No known key found for this signature in database
GPG Key ID: 712D413B3A9FED5C
17 changed files with 137 additions and 683 deletions

639
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,6 @@ napi-derive = "2.16.2"
napi-build = "2.1.3"
argon2 = "0.5.3"
async-trait = "0.1.80"
basen = "0.1.0"
bcrypt = "0.15.1"
cfg-if = "1.0.0"
@ -19,9 +18,7 @@ convert_case = "0.6.0"
cuid2 = "0.1.2"
emojis = "0.6.1"
idna = "0.5.0"
jsonschema = "0.17.1"
once_cell = "1.19.0"
parse-display = "0.9.0"
pretty_assertions = "1.4.0"
proc-macro2 = "1.0.79"
quote = "1.0.36"
@ -29,7 +26,6 @@ rand = "0.8.5"
redis = "0.25.3"
regex = "1.10.4"
rmp-serde = "1.2.0"
schemars = "0.8.16"
sea-orm = "0.12.15"
serde = "1.0.197"
serde_json = "1.0.115"

View File

@ -18,7 +18,6 @@ napi = { workspace = true, optional = true, default-features = false, features =
napi-derive = { workspace = true, optional = true }
argon2 = { workspace = true, features = ["std"] }
async-trait = { workspace = true }
basen = { workspace = true }
bcrypt = { workspace = true }
cfg-if = { workspace = true }
@ -26,14 +25,11 @@ chrono = { workspace = true }
cuid2 = { workspace = true }
emojis = { workspace = true }
idna = { workspace = true }
jsonschema = { workspace = true }
once_cell = { workspace = true }
parse-display = { workspace = true }
rand = { workspace = true }
redis = { workspace = true }
regex = { workspace = true }
rmp-serde = { workspace = true }
schemars = { workspace = true, features = ["chrono"] }
sea-orm = { workspace = true, features = ["sqlx-postgres", "runtime-tokio-rustls"] }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }

View File

@ -1129,6 +1129,13 @@ export enum PushNotificationKind {
ReadAllNotifications = 'readAllNotifications'
}
export function sendPushNotification(receiverUserId: string, kind: PushNotificationKind, content: any): Promise<void>
export enum ChatEvent {
Message = 'message',
Read = 'read',
Deleted = 'deleted',
Typing = 'typing'
}
export function publishToChatStream(senderUserId: string, receiverUserId: string, kind: ChatEvent, object: any): void
/** Initializes Cuid2 generator. Must be called before any [create_id]. */
export function initIdGenerator(length: number, fingerprint: string): void
export function getTimestamp(id: string): number

View File

@ -310,7 +310,7 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}
const { loadEnv, loadConfig, stringToAcct, acctToString, addNoteToAntenna, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getNoteSummary, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, PushNotificationKind, sendPushNotification, initIdGenerator, getTimestamp, genId, secureRndstr } = nativeBinding
const { loadEnv, loadConfig, stringToAcct, acctToString, addNoteToAntenna, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getNoteSummary, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, AntennaSrcEnum, DriveFileUsageHintEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, PushNotificationKind, sendPushNotification, ChatEvent, publishToChatStream, initIdGenerator, getTimestamp, genId, secureRndstr } = nativeBinding
module.exports.loadEnv = loadEnv
module.exports.loadConfig = loadConfig
@ -355,6 +355,8 @@ module.exports.UserProfileFfvisibilityEnum = UserProfileFfvisibilityEnum
module.exports.UserProfileMutingnotificationtypesEnum = UserProfileMutingnotificationtypesEnum
module.exports.PushNotificationKind = PushNotificationKind
module.exports.sendPushNotification = sendPushNotification
module.exports.ChatEvent = ChatEvent
module.exports.publishToChatStream = publishToChatStream
module.exports.initIdGenerator = initIdGenerator
module.exports.getTimestamp = getTimestamp
module.exports.genId = genId

View File

@ -1,9 +0,0 @@
#[derive(thiserror::Error, Debug, PartialEq, Eq)]
pub enum Error {
#[error("Failed to parse string: {0}")]
ParseError(#[from] parse_display::ParseError),
#[error("Database error: {0}")]
DbError(#[from] sea_orm::DbErr),
#[error("Requested entity not found")]
NotFound,
}

View File

@ -1,4 +1 @@
pub mod entity;
pub mod error;
// pub mod repository;
pub mod schema;

View File

@ -1,31 +0,0 @@
use async_trait::async_trait;
use schemars::JsonSchema;
use super::error::Error;
/// Repositories have a packer that converts a database model to its
/// corresponding API schema.
#[async_trait]
pub trait Repository<T: JsonSchema> {
async fn pack(self) -> Result<T, Error>;
/// Retrieves one model by its id and pack it.
async fn pack_by_id(id: String) -> Result<T, Error>;
}
mod macros {
/// Provides the default implementation of
/// [crate::model::repository::Repository::pack_by_id].
macro_rules! impl_pack_by_id {
($a:ty, $b:ident) => {
match <$a>::find_by_id($b)
.one(crate::database::get_database()?)
.await?
{
None => Err(Error::NotFound),
Some(m) => m.pack().await,
}
};
}
pub(crate) use impl_pack_by_id;
}

View File

@ -1,18 +0,0 @@
use jsonschema::JSONSchema;
use schemars::{schema_for, JsonSchema};
/// Structs of schema defitions implement this trait in order to
/// provide the JSON Schema validator [`jsonschema::JSONSchema`].
pub trait Schema<T: JsonSchema> {
/// Returns the validator of [JSON Schema Draft
/// 7](https://json-schema.org/specification-links.html#draft-7) with the
/// default settings of [`schemars::gen::SchemaSettings`].
fn validator() -> JSONSchema {
let root = schema_for!(T);
let schema = serde_json::to_value(&root).expect("Schema definition invalid");
JSONSchema::options()
.with_draft(jsonschema::Draft::Draft7)
.compile(&schema)
.expect("Unable to compile schema")
}
}

View File

@ -1,4 +1,5 @@
pub mod antenna;
pub mod chat;
use crate::config::CONFIG;
use crate::database::redis_conn;
@ -51,7 +52,7 @@ pub enum Error {
pub fn publish_to_stream(
stream: &Stream,
kind: Option<&str>,
kind: Option<String>,
value: Option<String>,
) -> Result<(), Error> {
let message = if let Some(kind) = kind {

View File

@ -4,7 +4,7 @@ use crate::service::stream::{publish_to_stream, Error, Stream};
pub fn publish(antenna_id: String, note: &note::Model) -> Result<(), Error> {
publish_to_stream(
&Stream::Antenna { antenna_id },
Some("note"),
Some("note".to_string()),
Some(serde_json::to_string(note)?),
)
}

View File

@ -0,0 +1,31 @@
use crate::service::stream::{publish_to_stream, Error, Stream};
#[derive(strum::Display)]
#[crate::export(string_enum = "camelCase")]
pub enum ChatEvent {
#[strum(serialize = "message")]
Message,
#[strum(serialize = "read")]
Read,
#[strum(serialize = "deleted")]
Deleted,
#[strum(serialize = "typing")]
Typing,
}
#[crate::export(js_name = "publishToChatStream")]
pub fn publish(
sender_user_id: String,
receiver_user_id: String,
kind: ChatEvent,
object: &serde_json::Value, // TODO?: change this to enum
) -> Result<(), Error> {
publish_to_stream(
&Stream::Chat {
sender_user_id,
receiver_user_id,
},
Some(kind.to_string()),
Some(serde_json::to_string(object)?),
)
}

View File

@ -2,7 +2,7 @@ import {
publishMainStream,
publishGroupMessagingStream,
} from "@/services/stream.js";
import { publishMessagingStream } from "@/services/stream.js";
import { publishToChatStream, ChatEvent } from "backend-rs";
import { publishMessagingIndexStream } from "@/services/stream.js";
import { sendPushNotification, PushNotificationKind } from "backend-rs";
import type { User, IRemoteUser } from "@/models/entities/user.js";
@ -54,7 +54,7 @@ export async function readUserMessagingMessage(
);
// Publish event
publishMessagingStream(otherpartyId, userId, "read", messageIds);
publishToChatStream(otherpartyId, userId, ChatEvent.Read, messageIds);
publishMessagingIndexStream(userId, "read", messageIds);
if (!(await Users.getHasUnreadMessagingMessage(userId))) {

View File

@ -4,7 +4,6 @@ import readNote from "@/services/note/read.js";
import type { User } from "@/models/entities/user.js";
import type { Channel as ChannelModel } from "@/models/entities/channel.js";
import {
Users,
Followings,
Mutings,
RenoteMutings,
@ -18,8 +17,8 @@ import type { UserProfile } from "@/models/entities/user-profile.js";
import {
publishChannelStream,
publishGroupMessagingStream,
publishMessagingStream,
} from "@/services/stream.js";
import { publishToChatStream, ChatEvent } from "backend-rs";
import type { UserGroup } from "@/models/entities/user-group.js";
import type { Packed } from "@/misc/schema.js";
import { readNotification } from "@/server/api/common/read-notification.js";
@ -525,10 +524,10 @@ export default class Connection {
}) {
if (this.user) {
if (param.partner) {
publishMessagingStream(
publishToChatStream(
param.partner,
this.user.id,
"typing",
ChatEvent.Typing,
this.user.id,
);
} else if (param.group) {

View File

@ -10,12 +10,13 @@ import {
import {
genId,
sendPushNotification,
publishToChatStream,
toPuny,
PushNotificationKind,
ChatEvent
} from "backend-rs";
import type { MessagingMessage } from "@/models/entities/messaging-message.js";
import {
publishMessagingStream,
publishMessagingIndexStream,
publishMainStream,
publishGroupMessagingStream,
@ -56,10 +57,10 @@ export async function createMessage(
if (recipientUser) {
if (Users.isLocalUser(user)) {
// 自分のストリーム
publishMessagingStream(
publishToChatStream(
message.userId,
recipientUser.id,
"message",
ChatEvent.Message,
messageObj,
);
publishMessagingIndexStream(message.userId, "message", messageObj);
@ -68,10 +69,10 @@ export async function createMessage(
if (Users.isLocalUser(recipientUser)) {
// 相手のストリーム
publishMessagingStream(
publishToChatStream(
recipientUser.id,
message.userId,
"message",
ChatEvent.Message,
messageObj,
);
publishMessagingIndexStream(recipientUser.id, "message", messageObj);

View File

@ -1,10 +1,8 @@
import { config } from "@/config.js";
import { MessagingMessages, Users } from "@/models/index.js";
import type { MessagingMessage } from "@/models/entities/messaging-message.js";
import {
publishGroupMessagingStream,
publishMessagingStream,
} from "@/services/stream.js";
import { publishGroupMessagingStream } from "@/services/stream.js";
import { publishToChatStream, ChatEvent } from "backend-rs";
import { renderActivity } from "@/remote/activitypub/renderer/index.js";
import renderDelete from "@/remote/activitypub/renderer/delete.js";
import renderTombstone from "@/remote/activitypub/renderer/tombstone.js";
@ -21,17 +19,17 @@ async function postDeleteMessage(message: MessagingMessage) {
const recipient = await Users.findOneByOrFail({ id: message.recipientId });
if (Users.isLocalUser(user))
publishMessagingStream(
publishToChatStream(
message.userId,
message.recipientId,
"deleted",
ChatEvent.Deleted,
message.id,
);
if (Users.isLocalUser(recipient))
publishMessagingStream(
publishToChatStream(
message.recipientId,
message.userId,
"deleted",
ChatEvent.Deleted,
message.id,
);

View File

@ -17,7 +17,7 @@ import type {
InternalStreamTypes,
MainStreamTypes,
MessagingIndexStreamTypes,
MessagingStreamTypes,
// MessagingStreamTypes,
NoteStreamTypes,
UserListStreamTypes,
UserStreamTypes,
@ -147,18 +147,19 @@ class Publisher {
// );
// };
public publishMessagingStream = <K extends keyof MessagingStreamTypes>(
userId: User["id"],
otherpartyId: User["id"],
type: K,
value?: MessagingStreamTypes[K],
): void => {
this.publish(
`messagingStream:${userId}-${otherpartyId}`,
type,
typeof value === "undefined" ? null : value,
);
};
/* ported to backend-rs */
// public publishMessagingStream = <K extends keyof MessagingStreamTypes>(
// userId: User["id"],
// otherpartyId: User["id"],
// type: K,
// value?: MessagingStreamTypes[K],
// ): void => {
// this.publish(
// `messagingStream:${userId}-${otherpartyId}`,
// type,
// typeof value === "undefined" ? null : value,
// );
// };
public publishGroupMessagingStream = <
K extends keyof GroupMessagingStreamTypes,
@ -219,7 +220,7 @@ export const publishNotesStream = publisher.publishNotesStream;
export const publishChannelStream = publisher.publishChannelStream;
export const publishUserListStream = publisher.publishUserListStream;
// export const publishAntennaStream = publisher.publishAntennaStream;
export const publishMessagingStream = publisher.publishMessagingStream;
// export const publishMessagingStream = publisher.publishMessagingStream;
export const publishGroupMessagingStream =
publisher.publishGroupMessagingStream;
export const publishMessagingIndexStream =