Merge branch 'develop' into iceshrimp_mastodon
This commit is contained in:
commit
c92ef5796a
67 changed files with 414 additions and 440 deletions
31
Cargo.lock
generated
31
Cargo.lock
generated
|
@ -226,7 +226,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"strum 0.26.2",
|
|
||||||
"sysinfo",
|
"sysinfo",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -2644,12 +2643,6 @@ dependencies = [
|
||||||
"untrusted",
|
"untrusted",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustversion"
|
|
||||||
version = "1.0.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
@ -2712,7 +2705,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"strum 0.25.0",
|
"strum",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -3240,28 +3233,6 @@ version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "strum"
|
|
||||||
version = "0.26.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29"
|
|
||||||
dependencies = [
|
|
||||||
"strum_macros",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "strum_macros"
|
|
||||||
version = "0.26.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946"
|
|
||||||
dependencies = [
|
|
||||||
"heck 0.4.1",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"rustversion",
|
|
||||||
"syn 2.0.66",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
|
|
@ -35,7 +35,6 @@ sea-orm = { version = "0.12.15", default-features = false }
|
||||||
serde = { version = "1.0.203", default-features = false }
|
serde = { version = "1.0.203", default-features = false }
|
||||||
serde_json = { version = "1.0.117", default-features = false }
|
serde_json = { version = "1.0.117", default-features = false }
|
||||||
serde_yaml = { version = "0.9.34", default-features = false }
|
serde_yaml = { version = "0.9.34", default-features = false }
|
||||||
strum = { version = "0.26.2", default-features = false }
|
|
||||||
syn = { version = "2.0.66", default-features = false }
|
syn = { version = "2.0.66", default-features = false }
|
||||||
sysinfo = { version = "0.30.12", default-features = false }
|
sysinfo = { version = "0.30.12", default-features = false }
|
||||||
thiserror = { version = "1.0.61", default-features = false }
|
thiserror = { version = "1.0.61", default-features = false }
|
||||||
|
|
|
@ -7,6 +7,10 @@ Critical security updates are indicated by the :warning: icon.
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Add the ability to share posts via QR code
|
||||||
|
- Update the API document page (`/api-doc`)
|
||||||
|
- Fix bugs
|
||||||
|
|
||||||
- Ported Mastodon API support from Iceshrimp, with added Firefish extensions including push notifications, post languages, schedule post support, and more.
|
- Ported Mastodon API support from Iceshrimp, with added Firefish extensions including push notifications, post languages, schedule post support, and more.
|
||||||
- The old Mastodon API has been replaced with a new implementation based on Iceshrimp’s.
|
- The old Mastodon API has been replaced with a new implementation based on Iceshrimp’s.
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@ Firefish depends on the following software.
|
||||||
|
|
||||||
## Runtime dependencies
|
## Runtime dependencies
|
||||||
|
|
||||||
- At least [NodeJS](https://nodejs.org/en/) v18.19.0 (v20/v21 recommended)
|
- At least [NodeJS](https://nodejs.org/en/) v18.19.0 (v20/v22 recommended)
|
||||||
- At least [PostgreSQL](https://www.postgresql.org/) v12 (v16 recommended) with [PGroonga](https://pgroonga.github.io/) extension
|
- At least [PostgreSQL](https://www.postgresql.org/) v12 (v16 recommended) with [PGroonga](https://pgroonga.github.io/) extension
|
||||||
- At least [Redis](https://redis.io/) v7
|
- At least [Redis](https://redis.io/) v7 or [Valkey](https://valkey.io/) v7
|
||||||
- Web Proxy (one of the following)
|
- Web Proxy (one of the following)
|
||||||
- Caddy (recommended)
|
- Caddy (recommended)
|
||||||
- Nginx (recommended)
|
- Nginx (recommended)
|
||||||
|
@ -15,7 +15,7 @@ Firefish depends on the following software.
|
||||||
- Caching server (**optional**, one of the following)
|
- Caching server (**optional**, one of the following)
|
||||||
- [DragonflyDB](https://www.dragonflydb.io/)
|
- [DragonflyDB](https://www.dragonflydb.io/)
|
||||||
- [KeyDB](https://keydb.dev/)
|
- [KeyDB](https://keydb.dev/)
|
||||||
- Another [Redis](https://redis.io/) server
|
- Another [Redis](https://redis.io/) / [Valkey](https://valkey.io/) server
|
||||||
|
|
||||||
## Build dependencies
|
## Build dependencies
|
||||||
|
|
||||||
|
@ -30,8 +30,6 @@ This document shows an example procedure for installing these dependencies and F
|
||||||
|
|
||||||
If you want to use the pre-built container image, please refer to [`install-container.md`](./install-container.md).
|
If you want to use the pre-built container image, please refer to [`install-container.md`](./install-container.md).
|
||||||
|
|
||||||
If you do not prepare your environment as document, be sure to meet the minimum dependencies given at the bottom of the page.
|
|
||||||
|
|
||||||
Make sure that you can use the `sudo` command before proceeding.
|
Make sure that you can use the `sudo` command before proceeding.
|
||||||
|
|
||||||
## 1. Install dependencies
|
## 1. Install dependencies
|
||||||
|
|
|
@ -2,6 +2,17 @@
|
||||||
|
|
||||||
You can skip intermediate versions when upgrading from an old version, but please read the notices and follow the instructions for each intermediate version before [upgrading](./upgrade.md).
|
You can skip intermediate versions when upgrading from an old version, but please read the notices and follow the instructions for each intermediate version before [upgrading](./upgrade.md).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
The following environment variables are deprecated and no longer have any effect:
|
||||||
|
- `MK_ONLY_QUEUE`
|
||||||
|
- `MK_ONLY_SERVER`
|
||||||
|
- `MK_NO_DAEMONS`
|
||||||
|
- `MK_DISABLE_CLUSTERING`
|
||||||
|
- `MK_VERBOSE`
|
||||||
|
- `MK_WITH_LOG_TIME`
|
||||||
|
- `MK_SLOW`
|
||||||
|
|
||||||
## v20240601
|
## v20240601
|
||||||
|
|
||||||
### For systemd/pm2 users
|
### For systemd/pm2 users
|
||||||
|
|
|
@ -543,7 +543,7 @@ existingAccount: "Existing account"
|
||||||
regenerate: "Regenerate"
|
regenerate: "Regenerate"
|
||||||
fontSize: "Font size"
|
fontSize: "Font size"
|
||||||
noFollowRequests: "You don't have any pending follow requests"
|
noFollowRequests: "You don't have any pending follow requests"
|
||||||
noSentFollowRequests: "You don't have any sent follow requests"
|
noSentFollowRequests: "You haven't sent any follow requests"
|
||||||
openImageInNewTab: "Open images in new tab"
|
openImageInNewTab: "Open images in new tab"
|
||||||
dashboard: "Dashboard"
|
dashboard: "Dashboard"
|
||||||
local: "Local"
|
local: "Local"
|
||||||
|
@ -1581,7 +1581,7 @@ _ago:
|
||||||
future: "future"
|
future: "future"
|
||||||
justNow: "just now"
|
justNow: "just now"
|
||||||
secondsAgo: "{n}s ago"
|
secondsAgo: "{n}s ago"
|
||||||
minutesAgo: "{n}m ago"
|
minutesAgo: "{n}min ago"
|
||||||
hoursAgo: "{n}h ago"
|
hoursAgo: "{n}h ago"
|
||||||
daysAgo: "{n}d ago"
|
daysAgo: "{n}d ago"
|
||||||
weeksAgo: "{n}w ago"
|
weeksAgo: "{n}w ago"
|
||||||
|
@ -1591,7 +1591,7 @@ _later:
|
||||||
future: "future"
|
future: "future"
|
||||||
justNow: "right now"
|
justNow: "right now"
|
||||||
secondsAgo: "in {n}s"
|
secondsAgo: "in {n}s"
|
||||||
minutesAgo: "in {n}m"
|
minutesAgo: "in {n}min"
|
||||||
hoursAgo: "in {n}h"
|
hoursAgo: "in {n}h"
|
||||||
daysAgo: "in {n}d"
|
daysAgo: "in {n}d"
|
||||||
weeksAgo: "in {n}w"
|
weeksAgo: "in {n}w"
|
||||||
|
|
|
@ -6,7 +6,7 @@ rust-version = "1.74"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
napi = ["dep:napi", "dep:napi-derive"]
|
napi = ["dep:napi", "dep:napi-derive", "dep:napi-build"]
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
@ -39,7 +39,6 @@ sea-orm = { workspace = true, features = ["macros", "runtime-tokio-rustls", "sql
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
serde_yaml = { workspace = true }
|
serde_yaml = { workspace = true }
|
||||||
strum = { workspace = true, features = ["derive"] }
|
|
||||||
sysinfo = { workspace = true }
|
sysinfo = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["fs", "io-std", "io-util", "macros", "process", "rt-multi-thread", "signal", "sync", "time"] }
|
tokio = { workspace = true, features = ["fs", "io-std", "io-util", "macros", "process", "rt-multi-thread", "signal", "sync", "time"] }
|
||||||
|
@ -54,4 +53,4 @@ pretty_assertions = { workspace = true, features = ["std"] }
|
||||||
tokio-test = { workspace = true }
|
tokio-test = { workspace = true }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
napi-build = { workspace = true }
|
napi-build = { workspace = true, optional = true }
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
extern crate napi_build;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// watch the version in the project root package.json
|
// watch the version in the project root package.json
|
||||||
println!("cargo:rerun-if-changed=../../package.json");
|
println!("cargo:rerun-if-changed=../../package.json");
|
||||||
|
|
||||||
// napi
|
// napi
|
||||||
|
#[cfg(feature = "napi")]
|
||||||
napi_build::setup();
|
napi_build::setup();
|
||||||
}
|
}
|
||||||
|
|
43
packages/backend-rs/index.d.ts
vendored
43
packages/backend-rs/index.d.ts
vendored
|
@ -19,16 +19,6 @@ export const USER_ACTIVE_THRESHOLD: number
|
||||||
* * <https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers>
|
* * <https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers>
|
||||||
*/
|
*/
|
||||||
export const FILE_TYPE_BROWSERSAFE: string[]
|
export const FILE_TYPE_BROWSERSAFE: string[]
|
||||||
export interface EnvConfig {
|
|
||||||
onlyQueue: boolean
|
|
||||||
onlyServer: boolean
|
|
||||||
noDaemons: boolean
|
|
||||||
disableClustering: boolean
|
|
||||||
verbose: boolean
|
|
||||||
withLogTime: boolean
|
|
||||||
slow: boolean
|
|
||||||
}
|
|
||||||
export function loadEnv(): EnvConfig
|
|
||||||
export function fetchMeta(): Promise<Meta>
|
export function fetchMeta(): Promise<Meta>
|
||||||
export function updateMetaCache(): Promise<void>
|
export function updateMetaCache(): Promise<void>
|
||||||
export interface PugArgs {
|
export interface PugArgs {
|
||||||
|
@ -467,7 +457,7 @@ export function latestVersion(): Promise<string>
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export function nyaify(text: string, lang?: string | undefined | null): string
|
export function nyaify(text: string, lang?: string | undefined | null): string
|
||||||
/** Hashes the given password using [Argon2] algorithm. */
|
/** Hashes the given password using [argon2] algorithm. */
|
||||||
export function hashPassword(password: string): string
|
export function hashPassword(password: string): string
|
||||||
/** Checks whether the given password and hash match. */
|
/** Checks whether the given password and hash match. */
|
||||||
export function verifyPassword(password: string, hash: string): boolean
|
export function verifyPassword(password: string, hash: string): boolean
|
||||||
|
@ -1382,29 +1372,24 @@ export interface Webhook {
|
||||||
latestStatus: number | null
|
latestStatus: number | null
|
||||||
}
|
}
|
||||||
export function updateAntennasOnNewNote(note: Note, noteAuthor: Acct, noteMutedUsers: Array<string>): Promise<void>
|
export function updateAntennasOnNewNote(note: Note, noteAuthor: Acct, noteMutedUsers: Array<string>): Promise<void>
|
||||||
|
export function updateAntennaCache(): Promise<void>
|
||||||
export function watchNote(watcherId: string, noteAuthorId: string, noteId: string): Promise<void>
|
export function watchNote(watcherId: string, noteAuthorId: string, noteId: string): Promise<void>
|
||||||
export function unwatchNote(watcherId: string, noteId: string): Promise<void>
|
export function unwatchNote(watcherId: string, noteId: string): Promise<void>
|
||||||
export enum PushNotificationKind {
|
export enum PushNotificationKind {
|
||||||
Generic = 'generic',
|
Generic = 0,
|
||||||
Chat = 'chat',
|
Chat = 1,
|
||||||
ReadAllChats = 'readAllChats',
|
ReadAllChats = 2,
|
||||||
ReadAllChatsInTheRoom = 'readAllChatsInTheRoom',
|
ReadAllChatsInTheRoom = 3,
|
||||||
ReadNotifications = 'readNotifications',
|
ReadNotifications = 4,
|
||||||
ReadAllNotifications = 'readAllNotifications',
|
ReadAllNotifications = 5,
|
||||||
Mastodon = 'mastodon'
|
Mastodon = 6
|
||||||
}
|
}
|
||||||
export function sendPushNotification(receiverUserId: string, kind: PushNotificationKind, content: any): Promise<void>
|
export function sendPushNotification(receiverUserId: string, kind: PushNotificationKind, content: any): Promise<void>
|
||||||
export function publishToChannelStream(channelId: string, userId: string): Promise<void>
|
export function publishToChannelStream(channelId: string, userId: string): Promise<void>
|
||||||
export enum ChatEvent {
|
|
||||||
Message = 'message',
|
|
||||||
Read = 'read',
|
|
||||||
Deleted = 'deleted',
|
|
||||||
Typing = 'typing'
|
|
||||||
}
|
|
||||||
export function publishToChatStream(senderUserId: string, receiverUserId: string, kind: ChatEvent, object: any): Promise<void>
|
export function publishToChatStream(senderUserId: string, receiverUserId: string, kind: ChatEvent, object: any): Promise<void>
|
||||||
export enum ChatIndexEvent {
|
export enum ChatIndexEvent {
|
||||||
Message = 'message',
|
Message = 0,
|
||||||
Read = 'read'
|
Read = 1
|
||||||
}
|
}
|
||||||
export function publishToChatIndexStream(userId: string, kind: ChatIndexEvent, object: any): Promise<void>
|
export function publishToChatIndexStream(userId: string, kind: ChatIndexEvent, object: any): Promise<void>
|
||||||
export interface PackedEmoji {
|
export interface PackedEmoji {
|
||||||
|
@ -1427,6 +1412,12 @@ export interface AbuseUserReportLike {
|
||||||
comment: string
|
comment: string
|
||||||
}
|
}
|
||||||
export function publishToModerationStream(moderatorId: string, report: AbuseUserReportLike): Promise<void>
|
export function publishToModerationStream(moderatorId: string, report: AbuseUserReportLike): Promise<void>
|
||||||
|
export enum ChatEvent {
|
||||||
|
Message = 0,
|
||||||
|
Read = 1,
|
||||||
|
Deleted = 2,
|
||||||
|
Typing = 3
|
||||||
|
}
|
||||||
export function getTimestamp(id: string): number
|
export function getTimestamp(id: string): number
|
||||||
/**
|
/**
|
||||||
* The generated ID results in the form of `[8 chars timestamp] + [cuid2]`.
|
* The generated ID results in the form of `[8 chars timestamp] + [cuid2]`.
|
||||||
|
|
|
@ -310,7 +310,7 @@ if (!nativeBinding) {
|
||||||
throw new Error(`Failed to load native binding`)
|
throw new Error(`Failed to load native binding`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, loadEnv, fetchMeta, updateMetaCache, metaToPugArgs, loadConfig, stringToAcct, acctToString, fetchNodeinfo, nodeinfo_2_1, nodeinfo_2_0, Protocol, Inbound, Outbound, greet, initializeRustLogger, showServerInfo, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, sqlRegexEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, isQuote, isSafeUrl, latestVersion, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, cpuInfo, cpuUsage, memoryUsage, storageUsage, AntennaSrc, DriveFileUsageHint, MutedNoteReason, NoteVisibility, NotificationType, PageVisibility, PollNoteVisibility, PushSubscriptionType, RelayStatus, UserEmojiModPerm, UserProfileFfvisibility, UserProfileMutingNotificationTypes, updateAntennasOnNewNote, watchNote, unwatchNote, PushNotificationKind, sendPushNotification, publishToChannelStream, ChatEvent, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding
|
const { SECOND, MINUTE, HOUR, DAY, USER_ONLINE_THRESHOLD, USER_ACTIVE_THRESHOLD, FILE_TYPE_BROWSERSAFE, fetchMeta, updateMetaCache, metaToPugArgs, loadConfig, stringToAcct, acctToString, fetchNodeinfo, nodeinfo_2_1, nodeinfo_2_0, Protocol, Inbound, Outbound, greet, initializeRustLogger, showServerInfo, isBlockedServer, isSilencedServer, isAllowedServer, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, sqlRegexEscape, safeForSql, formatMilliseconds, getImageSizeFromUrl, getNoteSummary, isQuote, isSafeUrl, latestVersion, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, removeOldAttestationChallenges, cpuInfo, cpuUsage, memoryUsage, storageUsage, AntennaSrc, DriveFileUsageHint, MutedNoteReason, NoteVisibility, NotificationType, PageVisibility, PollNoteVisibility, PushSubscriptionType, RelayStatus, UserEmojiModPerm, UserProfileFfvisibility, UserProfileMutingNotificationTypes, updateAntennasOnNewNote, updateAntennaCache, watchNote, unwatchNote, PushNotificationKind, sendPushNotification, publishToChannelStream, publishToChatStream, ChatIndexEvent, publishToChatIndexStream, publishToBroadcastStream, publishToGroupChatStream, publishToModerationStream, ChatEvent, getTimestamp, genId, genIdAt, generateSecureRandomString, generateUserToken } = nativeBinding
|
||||||
|
|
||||||
module.exports.SECOND = SECOND
|
module.exports.SECOND = SECOND
|
||||||
module.exports.MINUTE = MINUTE
|
module.exports.MINUTE = MINUTE
|
||||||
|
@ -319,7 +319,6 @@ module.exports.DAY = DAY
|
||||||
module.exports.USER_ONLINE_THRESHOLD = USER_ONLINE_THRESHOLD
|
module.exports.USER_ONLINE_THRESHOLD = USER_ONLINE_THRESHOLD
|
||||||
module.exports.USER_ACTIVE_THRESHOLD = USER_ACTIVE_THRESHOLD
|
module.exports.USER_ACTIVE_THRESHOLD = USER_ACTIVE_THRESHOLD
|
||||||
module.exports.FILE_TYPE_BROWSERSAFE = FILE_TYPE_BROWSERSAFE
|
module.exports.FILE_TYPE_BROWSERSAFE = FILE_TYPE_BROWSERSAFE
|
||||||
module.exports.loadEnv = loadEnv
|
|
||||||
module.exports.fetchMeta = fetchMeta
|
module.exports.fetchMeta = fetchMeta
|
||||||
module.exports.updateMetaCache = updateMetaCache
|
module.exports.updateMetaCache = updateMetaCache
|
||||||
module.exports.metaToPugArgs = metaToPugArgs
|
module.exports.metaToPugArgs = metaToPugArgs
|
||||||
|
@ -379,18 +378,19 @@ module.exports.UserEmojiModPerm = UserEmojiModPerm
|
||||||
module.exports.UserProfileFfvisibility = UserProfileFfvisibility
|
module.exports.UserProfileFfvisibility = UserProfileFfvisibility
|
||||||
module.exports.UserProfileMutingNotificationTypes = UserProfileMutingNotificationTypes
|
module.exports.UserProfileMutingNotificationTypes = UserProfileMutingNotificationTypes
|
||||||
module.exports.updateAntennasOnNewNote = updateAntennasOnNewNote
|
module.exports.updateAntennasOnNewNote = updateAntennasOnNewNote
|
||||||
|
module.exports.updateAntennaCache = updateAntennaCache
|
||||||
module.exports.watchNote = watchNote
|
module.exports.watchNote = watchNote
|
||||||
module.exports.unwatchNote = unwatchNote
|
module.exports.unwatchNote = unwatchNote
|
||||||
module.exports.PushNotificationKind = PushNotificationKind
|
module.exports.PushNotificationKind = PushNotificationKind
|
||||||
module.exports.sendPushNotification = sendPushNotification
|
module.exports.sendPushNotification = sendPushNotification
|
||||||
module.exports.publishToChannelStream = publishToChannelStream
|
module.exports.publishToChannelStream = publishToChannelStream
|
||||||
module.exports.ChatEvent = ChatEvent
|
|
||||||
module.exports.publishToChatStream = publishToChatStream
|
module.exports.publishToChatStream = publishToChatStream
|
||||||
module.exports.ChatIndexEvent = ChatIndexEvent
|
module.exports.ChatIndexEvent = ChatIndexEvent
|
||||||
module.exports.publishToChatIndexStream = publishToChatIndexStream
|
module.exports.publishToChatIndexStream = publishToChatIndexStream
|
||||||
module.exports.publishToBroadcastStream = publishToBroadcastStream
|
module.exports.publishToBroadcastStream = publishToBroadcastStream
|
||||||
module.exports.publishToGroupChatStream = publishToGroupChatStream
|
module.exports.publishToGroupChatStream = publishToGroupChatStream
|
||||||
module.exports.publishToModerationStream = publishToModerationStream
|
module.exports.publishToModerationStream = publishToModerationStream
|
||||||
|
module.exports.ChatEvent = ChatEvent
|
||||||
module.exports.getTimestamp = getTimestamp
|
module.exports.getTimestamp = getTimestamp
|
||||||
module.exports.genId = genId
|
module.exports.genId = genId
|
||||||
module.exports.genIdAt = genIdAt
|
module.exports.genIdAt = genIdAt
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
//! Environment options
|
|
||||||
|
|
||||||
// FIXME: Are these options used?
|
|
||||||
#[crate::export(object)]
|
|
||||||
pub struct EnvConfig {
|
|
||||||
pub only_queue: bool,
|
|
||||||
pub only_server: bool,
|
|
||||||
pub no_daemons: bool,
|
|
||||||
pub disable_clustering: bool,
|
|
||||||
pub verbose: bool,
|
|
||||||
pub with_log_time: bool,
|
|
||||||
pub slow: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[crate::export]
|
|
||||||
pub fn load_env() -> EnvConfig {
|
|
||||||
let node_env = std::env::var("NODE_ENV").unwrap_or_default().to_lowercase();
|
|
||||||
let is_testing = node_env == "test";
|
|
||||||
|
|
||||||
EnvConfig {
|
|
||||||
only_queue: std::env::var("MK_ONLY_QUEUE").is_ok(),
|
|
||||||
only_server: std::env::var("MK_ONLY_SERVER").is_ok(),
|
|
||||||
no_daemons: is_testing || std::env::var("MK_NO_DAEMONS").is_ok(),
|
|
||||||
disable_clustering: is_testing || std::env::var("MK_DISABLE_CLUSTERING").is_ok(),
|
|
||||||
verbose: std::env::var("MK_VERBOSE").is_ok(),
|
|
||||||
with_log_time: std::env::var("MK_WITH_LOG_TIME").is_ok(),
|
|
||||||
slow: std::env::var("MK_SLOW").is_ok(),
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
//! Server information
|
//! Server information
|
||||||
|
|
||||||
use crate::database::db_conn;
|
use crate::{database::db_conn, model::entity::meta};
|
||||||
use crate::model::entity::meta;
|
|
||||||
use sea_orm::{prelude::*, ActiveValue};
|
use sea_orm::{prelude::*, ActiveValue};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,5 @@ pub use meta::local_server_info;
|
||||||
pub use server::CONFIG;
|
pub use server::CONFIG;
|
||||||
|
|
||||||
pub mod constant;
|
pub mod constant;
|
||||||
pub mod environment;
|
|
||||||
pub mod meta;
|
pub mod meta;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
|
|
|
@ -2,12 +2,11 @@
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::env;
|
use std::{env, fs};
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
pub const VERSION: &str = macro_rs::read_version_from_package_json!();
|
pub const VERSION: &str = macro_rs::read_version_from_package_json!();
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[crate::export(object, use_nullable = false)]
|
||||||
struct ServerConfig {
|
struct ServerConfig {
|
||||||
|
@ -72,7 +71,7 @@ struct ServerConfig {
|
||||||
pub object_storage: Option<ObjectStorageConfig>,
|
pub object_storage: Option<ObjectStorageConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[crate::export(object, use_nullable = false)]
|
||||||
pub struct DbConfig {
|
pub struct DbConfig {
|
||||||
|
@ -85,7 +84,7 @@ pub struct DbConfig {
|
||||||
pub extra: Option<serde_json::Value>,
|
pub extra: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[crate::export(object, use_nullable = false)]
|
||||||
pub struct RedisConfig {
|
pub struct RedisConfig {
|
||||||
|
@ -100,7 +99,7 @@ pub struct RedisConfig {
|
||||||
pub prefix: Option<String>,
|
pub prefix: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[crate::export(object, use_nullable = false)]
|
||||||
pub struct TlsConfig {
|
pub struct TlsConfig {
|
||||||
|
@ -114,7 +113,7 @@ pub struct WorkerConfig {
|
||||||
pub queue: u32,
|
pub queue: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[crate::export(object, use_nullable = false)]
|
||||||
pub struct WorkerConfigInternal {
|
pub struct WorkerConfigInternal {
|
||||||
|
@ -122,7 +121,7 @@ pub struct WorkerConfigInternal {
|
||||||
pub queue: Option<u32>,
|
pub queue: Option<u32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[crate::export(object, use_nullable = false)]
|
||||||
pub struct IdConfig {
|
pub struct IdConfig {
|
||||||
|
@ -130,7 +129,7 @@ pub struct IdConfig {
|
||||||
pub fingerprint: Option<String>,
|
pub fingerprint: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[crate::export(object, use_nullable = false)]
|
||||||
pub struct SysLogConfig {
|
pub struct SysLogConfig {
|
||||||
|
@ -138,7 +137,7 @@ pub struct SysLogConfig {
|
||||||
pub port: u16,
|
pub port: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[crate::export(object, use_nullable = false)]
|
||||||
pub struct DeepLConfig {
|
pub struct DeepLConfig {
|
||||||
|
@ -147,7 +146,7 @@ pub struct DeepLConfig {
|
||||||
pub is_pro: Option<bool>,
|
pub is_pro: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[crate::export(object, use_nullable = false)]
|
||||||
pub struct LibreTranslateConfig {
|
pub struct LibreTranslateConfig {
|
||||||
|
@ -156,7 +155,7 @@ pub struct LibreTranslateConfig {
|
||||||
pub api_key: Option<String>,
|
pub api_key: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[crate::export(object, use_nullable = false)]
|
||||||
pub struct EmailConfig {
|
pub struct EmailConfig {
|
||||||
|
@ -169,7 +168,7 @@ pub struct EmailConfig {
|
||||||
pub use_implicit_ssl_tls: Option<bool>,
|
pub use_implicit_ssl_tls: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, use_nullable = false)]
|
#[crate::export(object, use_nullable = false)]
|
||||||
pub struct ObjectStorageConfig {
|
pub struct ObjectStorageConfig {
|
||||||
|
@ -278,7 +277,10 @@ pub fn load_config() -> Config {
|
||||||
None => hostname.clone(),
|
None => hostname.clone(),
|
||||||
};
|
};
|
||||||
let scheme = url.scheme().to_owned();
|
let scheme = url.scheme().to_owned();
|
||||||
let ws_scheme = scheme.replace("http", "ws");
|
let ws_scheme = match scheme.as_str() {
|
||||||
|
"http" => "ws",
|
||||||
|
_ => "wss",
|
||||||
|
};
|
||||||
|
|
||||||
let cluster_limits = match server_config.cluster_limits {
|
let cluster_limits = match server_config.cluster_limits {
|
||||||
Some(cl) => WorkerConfig {
|
Some(cl) => WorkerConfig {
|
||||||
|
@ -293,7 +295,7 @@ pub fn load_config() -> Config {
|
||||||
} else {
|
} else {
|
||||||
server_config.redis.prefix.clone()
|
server_config.redis.prefix.clone()
|
||||||
}
|
}
|
||||||
.unwrap_or(hostname.clone());
|
.unwrap_or_else(|| hostname.clone());
|
||||||
|
|
||||||
Config {
|
Config {
|
||||||
url: server_config.url,
|
url: server_config.url,
|
||||||
|
@ -344,7 +346,7 @@ pub fn load_config() -> Config {
|
||||||
hostname,
|
hostname,
|
||||||
redis_key_prefix,
|
redis_key_prefix,
|
||||||
scheme,
|
scheme,
|
||||||
ws_scheme,
|
ws_scheme: ws_scheme.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,16 +4,12 @@ use crate::database::{redis_conn, redis_key, RedisConnError};
|
||||||
use redis::{AsyncCommands, RedisError};
|
use redis::{AsyncCommands, RedisError};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(strum::Display, Debug)]
|
#[cfg_attr(test, derive(Debug))]
|
||||||
pub enum Category {
|
pub enum Category {
|
||||||
#[strum(serialize = "fetchUrl")]
|
|
||||||
FetchUrl,
|
FetchUrl,
|
||||||
#[strum(serialize = "blocking")]
|
|
||||||
Block,
|
Block,
|
||||||
#[strum(serialize = "following")]
|
|
||||||
Follow,
|
Follow,
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[strum(serialize = "usedOnlyForTesting")]
|
|
||||||
Test,
|
Test,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,9 +28,15 @@ fn prefix_key(key: &str) -> String {
|
||||||
redis_key(format!("cache:{}", key))
|
redis_key(format!("cache:{}", key))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn categorize(category: Category, key: &str) -> String {
|
fn categorize(category: Category, key: &str) -> String {
|
||||||
format!("{}:{}", category, key)
|
let prefix = match category {
|
||||||
|
Category::FetchUrl => "fetchUrl",
|
||||||
|
Category::Block => "blocking",
|
||||||
|
Category::Follow => "following",
|
||||||
|
#[cfg(test)]
|
||||||
|
Category::Test => "usedOnlyForTesting",
|
||||||
|
};
|
||||||
|
format!("{}:{}", prefix, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
//! Interfaces for accessing PostgreSQL and Redis
|
//! Interfaces for accessing PostgreSQL and Redis
|
||||||
|
|
||||||
pub use postgresql::db_conn;
|
pub use postgresql::get_conn as db_conn;
|
||||||
|
|
||||||
|
pub use redis::get_conn as redis_conn;
|
||||||
pub use redis::key as redis_key;
|
pub use redis::key as redis_key;
|
||||||
pub use redis::redis_conn;
|
|
||||||
pub use redis::RedisConnError;
|
pub use redis::RedisConnError;
|
||||||
|
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
|
|
|
@ -29,7 +29,7 @@ async fn init_conn() -> Result<&'static DbConn, DbErr> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an async PostgreSQL connection that can be used with [sea_orm] utilities.
|
/// Returns an async PostgreSQL connection that can be used with [sea_orm] utilities.
|
||||||
pub async fn db_conn() -> Result<&'static DbConn, DbErr> {
|
pub async fn get_conn() -> Result<&'static DbConn, DbErr> {
|
||||||
match DB_CONN.get() {
|
match DB_CONN.get() {
|
||||||
Some(conn) => Ok(conn),
|
Some(conn) => Ok(conn),
|
||||||
None => init_conn().await,
|
None => init_conn().await,
|
||||||
|
@ -38,11 +38,11 @@ pub async fn db_conn() -> Result<&'static DbConn, DbErr> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_test {
|
mod unit_test {
|
||||||
use super::db_conn;
|
use super::get_conn;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn connect() {
|
async fn connect() {
|
||||||
assert!(db_conn().await.is_ok());
|
assert!(get_conn().await.is_ok());
|
||||||
assert!(db_conn().await.is_ok());
|
assert!(get_conn().await.is_ok());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,8 +88,8 @@ pub enum RedisConnError {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an async [redis] connection managed by a [bb8] connection pool.
|
/// Returns an async [redis] connection managed by a [bb8] connection pool.
|
||||||
pub async fn redis_conn(
|
pub async fn get_conn() -> Result<PooledConnection<'static, RedisConnectionManager>, RedisConnError>
|
||||||
) -> Result<PooledConnection<'static, RedisConnectionManager>, RedisConnError> {
|
{
|
||||||
if !CONN_POOL.initialized() {
|
if !CONN_POOL.initialized() {
|
||||||
let init_res = init_conn_pool().await;
|
let init_res = init_conn_pool().await;
|
||||||
|
|
||||||
|
@ -114,19 +114,19 @@ pub fn key(key: impl ToString) -> String {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod unit_test {
|
mod unit_test {
|
||||||
use super::redis_conn;
|
use super::get_conn;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use redis::AsyncCommands;
|
use redis::AsyncCommands;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn connect() {
|
async fn connect() {
|
||||||
assert!(redis_conn().await.is_ok());
|
assert!(get_conn().await.is_ok());
|
||||||
assert!(redis_conn().await.is_ok());
|
assert!(get_conn().await.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn access() {
|
async fn access() {
|
||||||
let mut redis = redis_conn().await.unwrap();
|
let mut redis = get_conn().await.unwrap();
|
||||||
|
|
||||||
let key = "CARGO_UNIT_TEST_KEY";
|
let key = "CARGO_UNIT_TEST_KEY";
|
||||||
let value = "CARGO_UNIT_TEST_VALUE";
|
let value = "CARGO_UNIT_TEST_VALUE";
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use std::fmt;
|
use std::{fmt, str::FromStr};
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
#[crate::export(object)]
|
#[crate::export(object)]
|
||||||
pub struct Acct {
|
pub struct Acct {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
//!
|
//!
|
||||||
//! ref: <https://nodeinfo.diaspora.software/protocol.html>
|
//! ref: <https://nodeinfo.diaspora.software/protocol.html>
|
||||||
|
|
||||||
use crate::federation::nodeinfo::schema::*;
|
use crate::{federation::nodeinfo::schema::*, util::http_client};
|
||||||
use crate::util::http_client;
|
|
||||||
use isahc::AsyncReadResponseExt;
|
use isahc::AsyncReadResponseExt;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
@ -25,13 +24,13 @@ pub enum Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents the schema of `/.well-known/nodeinfo`.
|
/// Represents the schema of `/.well-known/nodeinfo`.
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize)]
|
||||||
pub struct NodeinfoLinks {
|
pub struct NodeinfoLinks {
|
||||||
links: Vec<NodeinfoLink>,
|
links: Vec<NodeinfoLink>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents one entry of `/.well-known/nodeinfo`.
|
/// Represents one entry of `/.well-known/nodeinfo`.
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize)]
|
||||||
pub struct NodeinfoLink {
|
pub struct NodeinfoLink {
|
||||||
rel: String,
|
rel: String,
|
||||||
href: String,
|
href: String,
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
//! NodeInfo generator
|
//! NodeInfo generator
|
||||||
|
|
||||||
use crate::config::{local_server_info, CONFIG};
|
use crate::{
|
||||||
use crate::database::{cache, db_conn};
|
config::{local_server_info, CONFIG},
|
||||||
use crate::federation::nodeinfo::schema::*;
|
database::{cache, db_conn},
|
||||||
use crate::model::entity::{note, user};
|
federation::nodeinfo::schema::*,
|
||||||
use sea_orm::{ColumnTrait, DbErr, EntityTrait, PaginatorTrait, QueryFilter};
|
model::entity::{note, user},
|
||||||
|
};
|
||||||
|
use sea_orm::prelude::*;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
@ -66,7 +68,7 @@ async fn generate_nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
|
||||||
let metadata = HashMap::from([
|
let metadata = HashMap::from([
|
||||||
(
|
(
|
||||||
"nodeName".to_string(),
|
"nodeName".to_string(),
|
||||||
json!(meta.name.unwrap_or(CONFIG.host.clone())),
|
json!(meta.name.unwrap_or_else(|| CONFIG.host.clone())),
|
||||||
),
|
),
|
||||||
("nodeDescription".to_string(), json!(meta.description)),
|
("nodeDescription".to_string(), json!(meta.description)),
|
||||||
("repositoryUrl".to_string(), json!(meta.repository_url)),
|
("repositoryUrl".to_string(), json!(meta.repository_url)),
|
||||||
|
@ -93,7 +95,7 @@ async fn generate_nodeinfo_2_1() -> Result<Nodeinfo21, Error> {
|
||||||
("proxyAccountName".to_string(), json!(meta.proxy_account_id)),
|
("proxyAccountName".to_string(), json!(meta.proxy_account_id)),
|
||||||
(
|
(
|
||||||
"themeColor".to_string(),
|
"themeColor".to_string(),
|
||||||
json!(meta.theme_color.unwrap_or("#31748f".to_string())),
|
json!(meta.theme_color.unwrap_or_else(|| "#31748f".to_string())),
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,8 @@ use std::collections::HashMap;
|
||||||
// * #[serde(tag = "version", rename = "2.1")] (https://github.com/3Hren/msgpack-rust/issues/318)
|
// * #[serde(tag = "version", rename = "2.1")] (https://github.com/3Hren/msgpack-rust/issues/318)
|
||||||
|
|
||||||
/// NodeInfo schema version 2.1. <https://nodeinfo.diaspora.software/docson/index.html#/ns/schema/2.1>
|
/// NodeInfo schema version 2.1. <https://nodeinfo.diaspora.software/docson/index.html#/ns/schema/2.1>
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Nodeinfo21 {
|
pub struct Nodeinfo21 {
|
||||||
/// The schema version, must be 2.1.
|
/// The schema version, must be 2.1.
|
||||||
|
@ -30,7 +31,8 @@ pub struct Nodeinfo21 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// NodeInfo schema version 2.0. <https://nodeinfo.diaspora.software/docson/index.html#/ns/schema/2.0>
|
/// NodeInfo schema version 2.0. <https://nodeinfo.diaspora.software/docson/index.html#/ns/schema/2.0>
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object, js_name = "Nodeinfo")]
|
#[crate::export(object, js_name = "Nodeinfo")]
|
||||||
pub struct Nodeinfo20 {
|
pub struct Nodeinfo20 {
|
||||||
|
@ -51,7 +53,8 @@ pub struct Nodeinfo20 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Metadata about server software in use (version 2.1).
|
/// Metadata about server software in use (version 2.1).
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Software21 {
|
pub struct Software21 {
|
||||||
/// The canonical name of this server software.
|
/// The canonical name of this server software.
|
||||||
|
@ -65,7 +68,8 @@ pub struct Software21 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Metadata about server software in use (version 2.0).
|
/// Metadata about server software in use (version 2.0).
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object)]
|
#[crate::export(object)]
|
||||||
pub struct Software20 {
|
pub struct Software20 {
|
||||||
|
@ -75,7 +79,8 @@ pub struct Software20 {
|
||||||
pub version: String,
|
pub version: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
#[crate::export(string_enum = "lowercase")]
|
#[crate::export(string_enum = "lowercase")]
|
||||||
pub enum Protocol {
|
pub enum Protocol {
|
||||||
|
@ -92,7 +97,8 @@ pub enum Protocol {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The third party sites this server can connect to via their application API.
|
/// The third party sites this server can connect to via their application API.
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object)]
|
#[crate::export(object)]
|
||||||
pub struct Services {
|
pub struct Services {
|
||||||
|
@ -103,7 +109,8 @@ pub struct Services {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The third party sites this server can retrieve messages from for combined display with regular traffic.
|
/// The third party sites this server can retrieve messages from for combined display with regular traffic.
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
#[crate::export(string_enum = "lowercase")]
|
#[crate::export(string_enum = "lowercase")]
|
||||||
pub enum Inbound {
|
pub enum Inbound {
|
||||||
|
@ -121,7 +128,8 @@ pub enum Inbound {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The third party sites this server can publish messages to on the behalf of a user.
|
/// The third party sites this server can publish messages to on the behalf of a user.
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
#[crate::export(string_enum = "lowercase")]
|
#[crate::export(string_enum = "lowercase")]
|
||||||
pub enum Outbound {
|
pub enum Outbound {
|
||||||
|
@ -158,7 +166,8 @@ pub enum Outbound {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Usage statistics for this server.
|
/// Usage statistics for this server.
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object)]
|
#[crate::export(object)]
|
||||||
pub struct Usage {
|
pub struct Usage {
|
||||||
|
@ -168,7 +177,8 @@ pub struct Usage {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// statistics about the users of this server.
|
/// statistics about the users of this server.
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object)]
|
#[crate::export(object)]
|
||||||
pub struct Users {
|
pub struct Users {
|
||||||
|
|
|
@ -26,19 +26,19 @@ pub fn show_server_info() -> Result<(), SysinfoPoisonError> {
|
||||||
|
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"Hostname: {}",
|
"Hostname: {}",
|
||||||
System::host_name().unwrap_or("unknown".to_string())
|
System::host_name().unwrap_or_else(|| "unknown".to_string())
|
||||||
);
|
);
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"OS: {}",
|
"OS: {}",
|
||||||
System::long_os_version().unwrap_or("unknown".to_string())
|
System::long_os_version().unwrap_or_else(|| "unknown".to_string())
|
||||||
);
|
);
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"Kernel: {}",
|
"Kernel: {}",
|
||||||
System::kernel_version().unwrap_or("unknown".to_string())
|
System::kernel_version().unwrap_or_else(|| "unknown".to_string())
|
||||||
);
|
);
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
"CPU architecture: {}",
|
"CPU architecture: {}",
|
||||||
System::cpu_arch().unwrap_or("unknown".to_string())
|
System::cpu_arch().unwrap_or_else(|| "unknown".to_string())
|
||||||
);
|
);
|
||||||
tracing::info!("CPU threads: {}", system_info.cpus().len());
|
tracing::info!("CPU threads: {}", system_info.cpus().len());
|
||||||
tracing::info!("Total memory: {} MiB", system_info.total_memory() / 1048576);
|
tracing::info!("Total memory: {} MiB", system_info.total_memory() / 1048576);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use crate::database::cache;
|
use crate::{database::cache, util::http_client};
|
||||||
use crate::util::http_client;
|
|
||||||
use image::{io::Reader, ImageError, ImageFormat};
|
use image::{io::Reader, ImageError, ImageFormat};
|
||||||
use isahc::ReadResponseExt;
|
use isahc::AsyncReadResponseExt;
|
||||||
use nom_exif::{parse_jpeg_exif, EntryValue, ExifTag};
|
use nom_exif::{parse_jpeg_exif, EntryValue, ExifTag};
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
@ -41,7 +40,7 @@ const BROWSER_SAFE_IMAGE_TYPES: [ImageFormat; 8] = [
|
||||||
|
|
||||||
static MTX_GUARD: Mutex<()> = Mutex::const_new(());
|
static MTX_GUARD: Mutex<()> = Mutex::const_new(());
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
#[crate::export(object)]
|
#[crate::export(object)]
|
||||||
pub struct ImageSize {
|
pub struct ImageSize {
|
||||||
pub width: u32,
|
pub width: u32,
|
||||||
|
@ -71,7 +70,7 @@ pub async fn get_image_size_from_url(url: &str) -> Result<ImageSize, Error> {
|
||||||
|
|
||||||
tracing::info!("retrieving image from {}", url);
|
tracing::info!("retrieving image from {}", url);
|
||||||
|
|
||||||
let mut response = http_client::client()?.get(url)?;
|
let mut response = http_client::client()?.get_async(url).await?;
|
||||||
|
|
||||||
if !response.status().is_success() {
|
if !response.status().is_success() {
|
||||||
tracing::info!("status: {}", response.status());
|
tracing::info!("status: {}", response.status());
|
||||||
|
@ -79,7 +78,7 @@ pub async fn get_image_size_from_url(url: &str) -> Result<ImageSize, Error> {
|
||||||
return Err(Error::Http(format!("Failed to get image from {}", url)));
|
return Err(Error::Http(format!("Failed to get image from {}", url)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let image_bytes = response.bytes()?;
|
let image_bytes = response.bytes().await?;
|
||||||
|
|
||||||
let reader = Reader::new(Cursor::new(&image_bytes)).with_guessed_format()?;
|
let reader = Reader::new(Cursor::new(&image_bytes)).with_guessed_format()?;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use crate::database::db_conn;
|
use crate::{
|
||||||
use crate::model::entity::{drive_file, note};
|
database::db_conn,
|
||||||
|
model::entity::{drive_file, note},
|
||||||
|
};
|
||||||
use sea_orm::{prelude::*, QuerySelect};
|
use sea_orm::{prelude::*, QuerySelect};
|
||||||
|
|
||||||
#[crate::export(object)]
|
#[crate::export(object)]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[crate::export(object)]
|
#[crate::export(object)]
|
||||||
pub struct PartialNoteToSummarize {
|
pub struct PartialNoteToSummarize {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
//! Fetch latest Firefish version from the Firefish repository
|
//! Fetch latest Firefish version from the Firefish repository
|
||||||
|
|
||||||
use crate::database::cache;
|
use crate::{database::cache, util::http_client};
|
||||||
use crate::util::http_client;
|
use isahc::AsyncReadResponseExt;
|
||||||
use isahc::ReadResponseExt;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
@ -30,7 +29,9 @@ async fn get_latest_version() -> Result<String, Error> {
|
||||||
version: String,
|
version: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut response = http_client::client()?.get(UPSTREAM_PACKAGE_JSON_URL)?;
|
let mut response = http_client::client()?
|
||||||
|
.get_async(UPSTREAM_PACKAGE_JSON_URL)
|
||||||
|
.await?;
|
||||||
|
|
||||||
if !response.status().is_success() {
|
if !response.status().is_success() {
|
||||||
tracing::info!("status: {}", response.status());
|
tracing::info!("status: {}", response.status());
|
||||||
|
@ -40,7 +41,7 @@ async fn get_latest_version() -> Result<String, Error> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let res_parsed: Response = serde_json::from_str(&response.text()?)?;
|
let res_parsed: Response = serde_json::from_str(&response.text().await?)?;
|
||||||
|
|
||||||
Ok(res_parsed.version)
|
Ok(res_parsed.version)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use argon2::{
|
||||||
Argon2,
|
Argon2,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Hashes the given password using [Argon2] algorithm.
|
/// Hashes the given password using [argon2] algorithm.
|
||||||
#[crate::export]
|
#[crate::export]
|
||||||
pub fn hash_password(password: &str) -> Result<String, password_hash::errors::Error> {
|
pub fn hash_password(password: &str) -> Result<String, password_hash::errors::Error> {
|
||||||
let salt = SaltString::generate(&mut OsRng);
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
use crate::config::local_server_info;
|
use crate::{config::local_server_info, database::db_conn, model::entity::emoji};
|
||||||
use crate::database::db_conn;
|
|
||||||
use crate::model::entity::emoji;
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use sea_orm::prelude::*;
|
use sea_orm::prelude::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[cfg_attr(test, derive(PartialEq, Debug))]
|
||||||
#[crate::export(object)]
|
#[crate::export(object)]
|
||||||
pub struct DecodedReaction {
|
pub struct DecodedReaction {
|
||||||
pub reaction: String,
|
pub reaction: String,
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// TODO: We want to get rid of this
|
// TODO: Migrate to Redis
|
||||||
|
|
||||||
use crate::database::db_conn;
|
use crate::{database::db_conn, model::entity::attestation_challenge};
|
||||||
use crate::model::entity::attestation_challenge;
|
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
use sea_orm::prelude::*;
|
use sea_orm::prelude::*;
|
||||||
|
|
||||||
|
|
|
@ -72,21 +72,17 @@ pub fn memory_usage() -> Result<Memory, SysinfoPoisonError> {
|
||||||
|
|
||||||
#[crate::export]
|
#[crate::export]
|
||||||
pub fn storage_usage() -> Option<Storage> {
|
pub fn storage_usage() -> Option<Storage> {
|
||||||
// Get the first disk that is actualy used.
|
// Get the first disk that is actualy used (has available space & has at least 1 GB total space).
|
||||||
let disks = Disks::new_with_refreshed_list();
|
let disks = Disks::new_with_refreshed_list();
|
||||||
let disk = disks
|
let disk = disks
|
||||||
.iter()
|
.iter()
|
||||||
.find(|disk| disk.available_space() > 0 && disk.total_space() > disk.available_space());
|
.find(|disk| disk.available_space() > 0 && disk.total_space() > 1024 * 1024 * 1024)?;
|
||||||
|
|
||||||
if let Some(disk) = disk {
|
let total = disk.total_space() as i64;
|
||||||
let total = disk.total_space() as i64;
|
let available = disk.available_space() as i64;
|
||||||
let available = disk.available_space() as i64;
|
|
||||||
return Some(Storage {
|
|
||||||
total,
|
|
||||||
used: total - available,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::debug!("failed to get stats");
|
Some(Storage {
|
||||||
None
|
total,
|
||||||
|
used: total - available,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
27
packages/backend-rs/src/service/antenna/cache.rs
Normal file
27
packages/backend-rs/src/service/antenna/cache.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
//! In-memory antennas cache handler
|
||||||
|
|
||||||
|
use crate::{database::db_conn, model::entity::antenna};
|
||||||
|
use sea_orm::prelude::*;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
static CACHE: Mutex<Option<Vec<antenna::Model>>> = Mutex::new(None);
|
||||||
|
|
||||||
|
fn set(antennas: &[antenna::Model]) {
|
||||||
|
let _ = CACHE
|
||||||
|
.lock()
|
||||||
|
.map(|mut cache| *cache = Some(antennas.to_owned()));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn update() -> Result<Vec<antenna::Model>, DbErr> {
|
||||||
|
tracing::debug!("updating cache");
|
||||||
|
let antennas = antenna::Entity::find().all(db_conn().await?).await?;
|
||||||
|
set(&antennas);
|
||||||
|
Ok(antennas)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn get() -> Result<Vec<antenna::Model>, DbErr> {
|
||||||
|
if let Some(cache) = CACHE.lock().ok().and_then(|cache| cache.clone()) {
|
||||||
|
return Ok(cache);
|
||||||
|
}
|
||||||
|
update().await
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
use crate::config::CONFIG;
|
use crate::{
|
||||||
use crate::database::{cache, db_conn};
|
config::CONFIG,
|
||||||
use crate::federation::acct::Acct;
|
database::{cache, db_conn},
|
||||||
use crate::model::entity::{antenna, blocking, following, note, sea_orm_active_enums::*};
|
federation::acct::Acct,
|
||||||
|
model::entity::{antenna, blocking, following, note, sea_orm_active_enums::*},
|
||||||
|
};
|
||||||
use sea_orm::{prelude::*, QuerySelect};
|
use sea_orm::{prelude::*, QuerySelect};
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
@ -61,7 +63,7 @@ pub async fn check_hit_antenna(
|
||||||
== note_author
|
== note_author
|
||||||
.host
|
.host
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or(CONFIG.host.clone())
|
.unwrap_or_else(|| CONFIG.host.clone())
|
||||||
.to_ascii_lowercase()
|
.to_ascii_lowercase()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
|
mod cache;
|
||||||
mod check_hit;
|
mod check_hit;
|
||||||
pub mod process_new_note;
|
pub mod process_new_note;
|
||||||
|
pub mod update;
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
use crate::database::{cache, db_conn, redis_conn, redis_key, RedisConnError};
|
use crate::{
|
||||||
use crate::federation::acct::Acct;
|
database::{cache, redis_conn, redis_key, RedisConnError},
|
||||||
use crate::misc::get_note_all_texts::{all_texts, PartialNoteToElaborate};
|
federation::acct::Acct,
|
||||||
use crate::model::entity::{antenna, note};
|
misc::get_note_all_texts::{all_texts, PartialNoteToElaborate},
|
||||||
use crate::service::antenna::check_hit::{check_hit_antenna, AntennaCheckError};
|
model::entity::note,
|
||||||
use crate::service::stream;
|
service::{
|
||||||
use crate::util::id::{get_timestamp, InvalidIdError};
|
antenna,
|
||||||
|
antenna::check_hit::{check_hit_antenna, AntennaCheckError},
|
||||||
|
stream,
|
||||||
|
},
|
||||||
|
util::id::{get_timestamp, InvalidIdError},
|
||||||
|
};
|
||||||
use redis::{streams::StreamMaxlen, AsyncCommands, RedisError};
|
use redis::{streams::StreamMaxlen, AsyncCommands, RedisError};
|
||||||
use sea_orm::prelude::*;
|
use sea_orm::prelude::*;
|
||||||
|
|
||||||
|
@ -28,23 +33,8 @@ pub enum Error {
|
||||||
|
|
||||||
// for napi export
|
// for napi export
|
||||||
// https://github.com/napi-rs/napi-rs/issues/2060
|
// https://github.com/napi-rs/napi-rs/issues/2060
|
||||||
type Antenna = antenna::Model;
|
|
||||||
type Note = note::Model;
|
type Note = note::Model;
|
||||||
|
|
||||||
// TODO?: it might be better to store this directly in memory
|
|
||||||
// (like fetch_meta) instead of Redis as it's used so much
|
|
||||||
async fn antennas() -> Result<Vec<Antenna>, Error> {
|
|
||||||
const CACHE_KEY: &str = "antennas";
|
|
||||||
|
|
||||||
if let Some(antennas) = cache::get::<Vec<Antenna>>(CACHE_KEY).await? {
|
|
||||||
Ok(antennas)
|
|
||||||
} else {
|
|
||||||
let antennas = antenna::Entity::find().all(db_conn().await?).await?;
|
|
||||||
cache::set(CACHE_KEY, &antennas, 5 * 60).await?;
|
|
||||||
Ok(antennas)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[crate::export]
|
#[crate::export]
|
||||||
pub async fn update_antennas_on_new_note(
|
pub async fn update_antennas_on_new_note(
|
||||||
note: Note,
|
note: Note,
|
||||||
|
@ -66,7 +56,7 @@ pub async fn update_antennas_on_new_note(
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// TODO: do this in parallel
|
// TODO: do this in parallel
|
||||||
for antenna in antennas().await?.iter() {
|
for antenna in antenna::cache::get().await?.iter() {
|
||||||
if note_muted_users.contains(&antenna.user_id) {
|
if note_muted_users.contains(&antenna.user_id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
7
packages/backend-rs/src/service/antenna/update.rs
Normal file
7
packages/backend-rs/src/service/antenna/update.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
//! This module is (currently) used in the TypeScript backend only.
|
||||||
|
|
||||||
|
#[crate::ts_export]
|
||||||
|
pub async fn update_antenna_cache() -> Result<(), sea_orm::DbErr> {
|
||||||
|
super::cache::update().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,6 +1,4 @@
|
||||||
use crate::database::db_conn;
|
use crate::{database::db_conn, model::entity::note_watching, util::id::gen_id_at};
|
||||||
use crate::model::entity::note_watching;
|
|
||||||
use crate::util::id::gen_id_at;
|
|
||||||
use sea_orm::{prelude::*, ActiveValue};
|
use sea_orm::{prelude::*, ActiveValue};
|
||||||
|
|
||||||
#[crate::export]
|
#[crate::export]
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
use crate::config::local_server_info;
|
use crate::{
|
||||||
use crate::database::db_conn;
|
config::local_server_info,
|
||||||
use crate::misc::get_note_summary::{get_note_summary, PartialNoteToSummarize};
|
database::db_conn,
|
||||||
use crate::model::entity::{access_token, app, sw_subscription};
|
misc::get_note_summary::{get_note_summary, PartialNoteToSummarize},
|
||||||
use crate::util::http_client;
|
model::entity::{access_token, app, sw_subscription},
|
||||||
use crate::util::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::*;
|
||||||
use web_push::{
|
use web_push::*;
|
||||||
ContentEncoding, IsahcWebPushClient, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder,
|
|
||||||
WebPushClient, WebPushError, WebPushMessageBuilder, WebPushPayload,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
|
@ -37,35 +35,23 @@ fn get_client() -> Result<IsahcWebPushClient, Error> {
|
||||||
.cloned()?)
|
.cloned()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(strum::Display, PartialEq)]
|
#[crate::export]
|
||||||
#[crate::export(string_enum = "camelCase")]
|
|
||||||
pub enum PushNotificationKind {
|
pub enum PushNotificationKind {
|
||||||
#[strum(serialize = "notification")]
|
|
||||||
Generic,
|
Generic,
|
||||||
#[strum(serialize = "unreadMessagingMessage")]
|
|
||||||
Chat,
|
Chat,
|
||||||
#[strum(serialize = "readAllMessagingMessages")]
|
|
||||||
ReadAllChats,
|
ReadAllChats,
|
||||||
#[strum(serialize = "readAllMessagingMessagesOfARoom")]
|
|
||||||
ReadAllChatsInTheRoom,
|
ReadAllChatsInTheRoom,
|
||||||
#[strum(serialize = "readNotifications")]
|
|
||||||
ReadNotifications,
|
ReadNotifications,
|
||||||
#[strum(serialize = "readAllNotifications")]
|
|
||||||
ReadAllNotifications,
|
ReadAllNotifications,
|
||||||
Mastodon,
|
Mastodon,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compact_content(
|
fn compact_content(mut content: serde_json::Value) -> Result<serde_json::Value, Error> {
|
||||||
kind: &PushNotificationKind,
|
if !content.is_object() {
|
||||||
mut content: serde_json::Value,
|
return Err(Error::InvalidContent("not a JSON object".to_string()));
|
||||||
) -> Result<serde_json::Value, Error> {
|
|
||||||
if kind != &PushNotificationKind::Generic {
|
|
||||||
return Ok(content);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let object = content
|
let object = content.as_object_mut().unwrap();
|
||||||
.as_object_mut()
|
|
||||||
.ok_or(Error::InvalidContent("not a JSON object".to_string()))?;
|
|
||||||
|
|
||||||
if !object.contains_key("note") {
|
if !object.contains_key("note") {
|
||||||
return Ok(content);
|
return Ok(content);
|
||||||
|
@ -265,24 +251,40 @@ pub async fn send_push_notification(
|
||||||
.all(db)
|
.all(db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let use_mastodon_api = matches!(kind, PushNotificationKind::Mastodon);
|
||||||
|
|
||||||
// TODO: refactoring
|
// TODO: refactoring
|
||||||
let mut payload = if kind == PushNotificationKind::Mastodon {
|
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
|
||||||
|
let label = match kind {
|
||||||
|
PushNotificationKind::Generic => "notification",
|
||||||
|
PushNotificationKind::Chat => "unreadMessagingMessage",
|
||||||
|
PushNotificationKind::ReadAllChats => "readAllMessagingMessages",
|
||||||
|
PushNotificationKind::ReadAllChatsInTheRoom => "readAllMessagingMessagesOfARoom",
|
||||||
|
PushNotificationKind::ReadNotifications => "readNotifications",
|
||||||
|
PushNotificationKind::ReadAllNotifications => "readAllNotifications",
|
||||||
|
// unreachable
|
||||||
|
_ => "unknown",
|
||||||
|
};
|
||||||
format!(
|
format!(
|
||||||
"{{\"type\":\"{}\",\"userId\":\"{}\",\"dateTime\":{},\"body\":{}}}",
|
"{{\"type\":\"{}\",\"userId\":\"{}\",\"dateTime\":{},\"body\":{}}}",
|
||||||
kind,
|
label,
|
||||||
receiver_user_id,
|
receiver_user_id,
|
||||||
chrono::Utc::now().timestamp_millis(),
|
chrono::Utc::now().timestamp_millis(),
|
||||||
serde_json::to_string(&compact_content(&kind, content.clone())?)?
|
match kind {
|
||||||
|
PushNotificationKind::Generic =>
|
||||||
|
serde_json::to_string(&compact_content(content.to_owned())?)?,
|
||||||
|
_ => serde_json::to_string(&content)?,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
tracing::trace!("payload: {}", payload);
|
tracing::trace!("payload: {}", payload);
|
||||||
|
|
||||||
let encoding = if kind == PushNotificationKind::Mastodon {
|
let encoding = if use_mastodon_api {
|
||||||
ContentEncoding::AesGcm
|
ContentEncoding::AesGcm
|
||||||
} else {
|
} else {
|
||||||
ContentEncoding::Aes128Gcm
|
ContentEncoding::Aes128Gcm
|
||||||
|
@ -290,18 +292,18 @@ pub async fn send_push_notification(
|
||||||
|
|
||||||
for subscription in subscriptions.iter() {
|
for subscription in subscriptions.iter() {
|
||||||
if !subscription.send_read_message
|
if !subscription.send_read_message
|
||||||
&& [
|
&& matches!(
|
||||||
PushNotificationKind::ReadAllChats,
|
kind,
|
||||||
PushNotificationKind::ReadAllChatsInTheRoom,
|
PushNotificationKind::ReadAllChats
|
||||||
PushNotificationKind::ReadAllNotifications,
|
| PushNotificationKind::ReadAllChatsInTheRoom
|
||||||
PushNotificationKind::ReadNotifications,
|
| PushNotificationKind::ReadAllNotifications
|
||||||
]
|
| PushNotificationKind::ReadNotifications
|
||||||
.contains(&kind)
|
)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if kind == PushNotificationKind::Mastodon {
|
if use_mastodon_api {
|
||||||
if subscription.app_access_token_id.is_none() {
|
if subscription.app_access_token_id.is_none() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,43 +6,58 @@ pub mod custom_emoji;
|
||||||
pub mod group_chat;
|
pub mod group_chat;
|
||||||
pub mod moderation;
|
pub mod moderation;
|
||||||
|
|
||||||
use crate::config::CONFIG;
|
use crate::{
|
||||||
use crate::database::{redis_conn, RedisConnError};
|
config::CONFIG,
|
||||||
|
database::{redis_conn, RedisConnError},
|
||||||
|
};
|
||||||
use redis::{AsyncCommands, RedisError};
|
use redis::{AsyncCommands, RedisError};
|
||||||
|
|
||||||
#[derive(strum::Display)]
|
|
||||||
pub enum Stream {
|
pub enum Stream {
|
||||||
#[strum(serialize = "internal")]
|
|
||||||
Internal,
|
Internal,
|
||||||
#[strum(serialize = "broadcast")]
|
|
||||||
CustomEmoji,
|
CustomEmoji,
|
||||||
#[strum(to_string = "adminStream:{moderator_id}")]
|
Moderation {
|
||||||
Moderation { moderator_id: String },
|
moderator_id: String,
|
||||||
#[strum(to_string = "user:{user_id}")]
|
},
|
||||||
User { user_id: String },
|
User {
|
||||||
#[strum(to_string = "channelStream:{channel_id}")]
|
user_id: String,
|
||||||
Channel { channel_id: String },
|
},
|
||||||
#[strum(to_string = "noteStream:{note_id}")]
|
Channel {
|
||||||
Note { note_id: String },
|
channel_id: String,
|
||||||
#[strum(serialize = "notesStream")]
|
},
|
||||||
|
Note {
|
||||||
|
note_id: String,
|
||||||
|
},
|
||||||
Notes,
|
Notes,
|
||||||
#[strum(to_string = "userListStream:{list_id}")]
|
UserList {
|
||||||
UserList { list_id: String },
|
list_id: String,
|
||||||
#[strum(to_string = "mainStream:{user_id}")]
|
},
|
||||||
Main { user_id: String },
|
Main {
|
||||||
#[strum(to_string = "driveStream:{user_id}")]
|
user_id: String,
|
||||||
Drive { user_id: String },
|
},
|
||||||
#[strum(to_string = "antennaStream:{antenna_id}")]
|
Drive {
|
||||||
Antenna { antenna_id: String },
|
user_id: String,
|
||||||
#[strum(to_string = "messagingStream:{sender_user_id}-{receiver_user_id}")]
|
},
|
||||||
|
Antenna {
|
||||||
|
antenna_id: String,
|
||||||
|
},
|
||||||
Chat {
|
Chat {
|
||||||
sender_user_id: String,
|
sender_user_id: String,
|
||||||
receiver_user_id: String,
|
receiver_user_id: String,
|
||||||
},
|
},
|
||||||
#[strum(to_string = "messagingStream:{group_id}")]
|
GroupChat {
|
||||||
GroupChat { group_id: String },
|
group_id: String,
|
||||||
#[strum(to_string = "messagingIndexStream:{user_id}")]
|
},
|
||||||
ChatIndex { user_id: String },
|
ChatIndex {
|
||||||
|
user_id: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[crate::export]
|
||||||
|
pub enum ChatEvent {
|
||||||
|
Message,
|
||||||
|
Read,
|
||||||
|
Deleted,
|
||||||
|
Typing,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
@ -59,14 +74,34 @@ pub enum Error {
|
||||||
|
|
||||||
pub async fn publish_to_stream(
|
pub async fn publish_to_stream(
|
||||||
stream: &Stream,
|
stream: &Stream,
|
||||||
kind: Option<String>,
|
kind: Option<&str>,
|
||||||
value: Option<String>,
|
value: Option<String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
let channel = match stream {
|
||||||
|
Stream::Internal => "internal".to_string(),
|
||||||
|
Stream::CustomEmoji => "broadcast".to_string(),
|
||||||
|
Stream::Moderation { moderator_id } => format!("adminStream:{moderator_id}"),
|
||||||
|
Stream::User { user_id } => format!("user:{user_id}"),
|
||||||
|
Stream::Channel { channel_id } => format!("channelStream:{channel_id}"),
|
||||||
|
Stream::Note { note_id } => format!("noteStream:{note_id}"),
|
||||||
|
Stream::Notes => "notesStream".to_string(),
|
||||||
|
Stream::UserList { list_id } => format!("userListStream:{list_id}"),
|
||||||
|
Stream::Main { user_id } => format!("mainStream:{user_id}"),
|
||||||
|
Stream::Drive { user_id } => format!("driveStream:{user_id}"),
|
||||||
|
Stream::Antenna { antenna_id } => format!("antennaStream:{antenna_id}"),
|
||||||
|
Stream::Chat {
|
||||||
|
sender_user_id,
|
||||||
|
receiver_user_id,
|
||||||
|
} => format!("messagingStream:{sender_user_id}-{receiver_user_id}"),
|
||||||
|
Stream::GroupChat { group_id } => format!("messagingStream:{group_id}"),
|
||||||
|
Stream::ChatIndex { user_id } => format!("messagingIndexStream:{user_id}"),
|
||||||
|
};
|
||||||
|
|
||||||
let message = if let Some(kind) = kind {
|
let message = if let Some(kind) = kind {
|
||||||
format!(
|
format!(
|
||||||
"{{\"type\":\"{}\",\"body\":{}}}",
|
"{{\"type\":\"{}\",\"body\":{}}}",
|
||||||
kind,
|
kind,
|
||||||
value.unwrap_or("null".to_string()),
|
value.unwrap_or_else(|| "null".to_string()),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
value.ok_or(Error::Value("Invalid streaming message".to_string()))?
|
value.ok_or(Error::Value("Invalid streaming message".to_string()))?
|
||||||
|
@ -76,28 +111,9 @@ pub async fn publish_to_stream(
|
||||||
.await?
|
.await?
|
||||||
.publish(
|
.publish(
|
||||||
&CONFIG.host,
|
&CONFIG.host,
|
||||||
format!("{{\"channel\":\"{}\",\"message\":{}}}", stream, message),
|
format!("{{\"channel\":\"{}\",\"message\":{}}}", channel, message),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod unit_test {
|
|
||||||
use super::Stream;
|
|
||||||
use pretty_assertions::assert_eq;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn channel_to_string() {
|
|
||||||
assert_eq!(Stream::Internal.to_string(), "internal");
|
|
||||||
assert_eq!(Stream::CustomEmoji.to_string(), "broadcast");
|
|
||||||
assert_eq!(
|
|
||||||
Stream::Moderation {
|
|
||||||
moderator_id: "9tb42br63g5apjcq".to_string()
|
|
||||||
}
|
|
||||||
.to_string(),
|
|
||||||
"adminStream:9tb42br63g5apjcq"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use crate::model::entity::note;
|
use crate::{
|
||||||
use crate::service::stream::{publish_to_stream, Error, Stream};
|
model::entity::note,
|
||||||
|
service::stream::{publish_to_stream, Error, Stream},
|
||||||
|
};
|
||||||
|
|
||||||
pub async fn publish(antenna_id: String, note: ¬e::Model) -> Result<(), Error> {
|
pub async fn publish(antenna_id: String, note: ¬e::Model) -> Result<(), Error> {
|
||||||
publish_to_stream(
|
publish_to_stream(
|
||||||
&Stream::Antenna { antenna_id },
|
&Stream::Antenna { antenna_id },
|
||||||
Some("note".to_string()),
|
Some("note"),
|
||||||
Some(serde_json::to_string(note)?),
|
Some(serde_json::to_string(note)?),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -4,7 +4,7 @@ use crate::service::stream::{publish_to_stream, Error, Stream};
|
||||||
pub async fn publish(channel_id: String, user_id: String) -> Result<(), Error> {
|
pub async fn publish(channel_id: String, user_id: String) -> Result<(), Error> {
|
||||||
publish_to_stream(
|
publish_to_stream(
|
||||||
&Stream::Channel { channel_id },
|
&Stream::Channel { channel_id },
|
||||||
Some("typing".to_string()),
|
Some("typing"),
|
||||||
Some(format!("\"{}\"", user_id)),
|
Some(format!("\"{}\"", user_id)),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,17 +1,4 @@
|
||||||
use crate::service::stream::{publish_to_stream, Error, Stream};
|
use crate::service::stream::{publish_to_stream, ChatEvent, 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
// We want to merge `kind` and `object` into a single enum
|
// We want to merge `kind` and `object` into a single enum
|
||||||
// https://github.com/napi-rs/napi-rs/issues/2036
|
// https://github.com/napi-rs/napi-rs/issues/2036
|
||||||
|
@ -23,12 +10,19 @@ pub async fn publish(
|
||||||
kind: ChatEvent,
|
kind: ChatEvent,
|
||||||
object: &serde_json::Value,
|
object: &serde_json::Value,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
let kind = match kind {
|
||||||
|
ChatEvent::Message => "message",
|
||||||
|
ChatEvent::Read => "read",
|
||||||
|
ChatEvent::Deleted => "deleted",
|
||||||
|
ChatEvent::Typing => "typing",
|
||||||
|
};
|
||||||
|
|
||||||
publish_to_stream(
|
publish_to_stream(
|
||||||
&Stream::Chat {
|
&Stream::Chat {
|
||||||
sender_user_id,
|
sender_user_id,
|
||||||
receiver_user_id,
|
receiver_user_id,
|
||||||
},
|
},
|
||||||
Some(kind.to_string()),
|
Some(kind),
|
||||||
Some(serde_json::to_string(object)?),
|
Some(serde_json::to_string(object)?),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
use crate::service::stream::{publish_to_stream, Error, Stream};
|
use crate::service::stream::{publish_to_stream, Error, Stream};
|
||||||
|
|
||||||
#[derive(strum::Display)]
|
#[crate::export]
|
||||||
#[crate::export(string_enum = "camelCase")]
|
|
||||||
pub enum ChatIndexEvent {
|
pub enum ChatIndexEvent {
|
||||||
#[strum(serialize = "message")]
|
|
||||||
Message,
|
Message,
|
||||||
#[strum(serialize = "read")]
|
|
||||||
Read,
|
Read,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,9 +15,14 @@ pub async fn publish(
|
||||||
kind: ChatIndexEvent,
|
kind: ChatIndexEvent,
|
||||||
object: &serde_json::Value,
|
object: &serde_json::Value,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
let kind = match kind {
|
||||||
|
ChatIndexEvent::Message => "message",
|
||||||
|
ChatIndexEvent::Read => "read",
|
||||||
|
};
|
||||||
|
|
||||||
publish_to_stream(
|
publish_to_stream(
|
||||||
&Stream::ChatIndex { user_id },
|
&Stream::ChatIndex { user_id },
|
||||||
Some(kind.to_string()),
|
Some(kind),
|
||||||
Some(serde_json::to_string(object)?),
|
Some(serde_json::to_string(object)?),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -21,7 +21,7 @@ pub struct PackedEmoji {
|
||||||
pub async fn publish(emoji: &PackedEmoji) -> Result<(), Error> {
|
pub async fn publish(emoji: &PackedEmoji) -> Result<(), Error> {
|
||||||
publish_to_stream(
|
publish_to_stream(
|
||||||
&Stream::CustomEmoji,
|
&Stream::CustomEmoji,
|
||||||
Some("emojiAdded".to_string()),
|
Some("emojiAdded"),
|
||||||
Some(format!("{{\"emoji\":{}}}", serde_json::to_string(emoji)?)),
|
Some(format!("{{\"emoji\":{}}}", serde_json::to_string(emoji)?)),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::service::stream::{chat::ChatEvent, publish_to_stream, Error, Stream};
|
use crate::service::stream::{publish_to_stream, ChatEvent, Error, Stream};
|
||||||
|
|
||||||
// We want to merge `kind` and `object` into a single enum
|
// We want to merge `kind` and `object` into a single enum
|
||||||
// https://github.com/napi-rs/napi-rs/issues/2036
|
// https://github.com/napi-rs/napi-rs/issues/2036
|
||||||
|
@ -9,9 +9,16 @@ pub async fn publish(
|
||||||
kind: ChatEvent,
|
kind: ChatEvent,
|
||||||
object: &serde_json::Value,
|
object: &serde_json::Value,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
let kind = match kind {
|
||||||
|
ChatEvent::Message => "message",
|
||||||
|
ChatEvent::Read => "read",
|
||||||
|
ChatEvent::Deleted => "deleted",
|
||||||
|
ChatEvent::Typing => "typing",
|
||||||
|
};
|
||||||
|
|
||||||
publish_to_stream(
|
publish_to_stream(
|
||||||
&Stream::GroupChat { group_id },
|
&Stream::GroupChat { group_id },
|
||||||
Some(kind.to_string()),
|
Some(kind),
|
||||||
Some(serde_json::to_string(object)?),
|
Some(serde_json::to_string(object)?),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -15,7 +15,7 @@ pub struct AbuseUserReportLike {
|
||||||
pub async fn publish(moderator_id: String, report: &AbuseUserReportLike) -> Result<(), Error> {
|
pub async fn publish(moderator_id: String, report: &AbuseUserReportLike) -> Result<(), Error> {
|
||||||
publish_to_stream(
|
publish_to_stream(
|
||||||
&Stream::Moderation { moderator_id },
|
&Stream::Moderation { moderator_id },
|
||||||
Some("newAbuseUserReport".to_string()),
|
Some("newAbuseUserReport"),
|
||||||
Some(serde_json::to_string(report)?),
|
Some(serde_json::to_string(report)?),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -20,13 +20,13 @@ static CLIENT: OnceCell<HttpClient> = OnceCell::new();
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// # use backend_rs::util::http_client::client;
|
/// # use backend_rs::util::http_client::client;
|
||||||
/// use isahc::ReadResponseExt;
|
/// use isahc::AsyncReadResponseExt;
|
||||||
///
|
///
|
||||||
/// # fn f() -> Result<(), Box<dyn std::error::Error>> {
|
/// # fn f() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
/// let mut response = client()?.get("https://example.com/")?;
|
/// let mut response = client()?.get_async("https://example.com/").await?;
|
||||||
///
|
///
|
||||||
/// if response.status().is_success() {
|
/// if response.status().is_success() {
|
||||||
/// println!("{}", response.text()?);
|
/// println!("{}", response.text().await?);
|
||||||
/// }
|
/// }
|
||||||
/// # Ok(())
|
/// # Ok(())
|
||||||
/// # }
|
/// # }
|
||||||
|
|
|
@ -79,7 +79,6 @@
|
||||||
"koa-mount": "4.0.0",
|
"koa-mount": "4.0.0",
|
||||||
"koa-remove-trailing-slashes": "2.0.3",
|
"koa-remove-trailing-slashes": "2.0.3",
|
||||||
"koa-send": "5.0.1",
|
"koa-send": "5.0.1",
|
||||||
"koa-slow": "2.1.0",
|
|
||||||
"mfm-js": "0.24.0",
|
"mfm-js": "0.24.0",
|
||||||
"mime-types": "2.1.35",
|
"mime-types": "2.1.35",
|
||||||
"msgpackr": "1.10.2",
|
"msgpackr": "1.10.2",
|
||||||
|
|
14
packages/backend/src/@types/koa-slow.d.ts
vendored
14
packages/backend/src/@types/koa-slow.d.ts
vendored
|
@ -1,14 +0,0 @@
|
||||||
declare module "koa-slow" {
|
|
||||||
import type { Middleware } from "koa";
|
|
||||||
|
|
||||||
interface ISlowOptions {
|
|
||||||
url?: RegExp;
|
|
||||||
delay?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function slow(options?: ISlowOptions): Middleware;
|
|
||||||
|
|
||||||
namespace slow {} // Hack
|
|
||||||
|
|
||||||
export = slow;
|
|
||||||
}
|
|
|
@ -3,7 +3,6 @@ import chalk from "chalk";
|
||||||
import Xev from "xev";
|
import Xev from "xev";
|
||||||
|
|
||||||
import Logger from "@/services/logger.js";
|
import Logger from "@/services/logger.js";
|
||||||
import { envOption } from "@/config.js";
|
|
||||||
import { inspect } from "node:util";
|
import { inspect } from "node:util";
|
||||||
|
|
||||||
// for typeorm
|
// for typeorm
|
||||||
|
@ -31,14 +30,14 @@ export default async function () {
|
||||||
const type = cluster.isPrimary ? "(master)" : "(worker)";
|
const type = cluster.isPrimary ? "(master)" : "(worker)";
|
||||||
process.title = `Firefish ${mode} ${type}`;
|
process.title = `Firefish ${mode} ${type}`;
|
||||||
|
|
||||||
if (cluster.isPrimary || envOption.disableClustering) {
|
if (cluster.isPrimary) {
|
||||||
await masterMain();
|
await masterMain();
|
||||||
if (cluster.isPrimary) {
|
if (cluster.isPrimary) {
|
||||||
ev.mount();
|
ev.mount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cluster.isWorker || envOption.disableClustering) {
|
if (cluster.isWorker) {
|
||||||
await workerMain();
|
await workerMain();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
updateMetaCache,
|
updateMetaCache,
|
||||||
type Config,
|
type Config,
|
||||||
} from "backend-rs";
|
} from "backend-rs";
|
||||||
import { config, envOption } from "@/config.js";
|
import { config } from "@/config.js";
|
||||||
import { db, initDb } from "@/db/postgre.js";
|
import { db, initDb } from "@/db/postgre.js";
|
||||||
import { inspect } from "node:util";
|
import { inspect } from "node:util";
|
||||||
|
|
||||||
|
@ -39,9 +39,7 @@ export async function masterMain() {
|
||||||
|
|
||||||
bootLogger.info("Firefish initialized");
|
bootLogger.info("Firefish initialized");
|
||||||
|
|
||||||
if (!envOption.disableClustering) {
|
await spawnWorkers(config.clusterLimits);
|
||||||
await spawnWorkers(config.clusterLimits);
|
|
||||||
}
|
|
||||||
|
|
||||||
bootLogger.info(
|
bootLogger.info(
|
||||||
`Now listening on port ${config.port} on ${config.url}`,
|
`Now listening on port ${config.port} on ${config.url}`,
|
||||||
|
@ -49,14 +47,12 @@ export async function masterMain() {
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!envOption.noDaemons) {
|
import("../daemons/server-stats.js").then((x) => x.default());
|
||||||
import("../daemons/server-stats.js").then((x) => x.default());
|
import("../daemons/queue-stats.js").then((x) => x.default());
|
||||||
import("../daemons/queue-stats.js").then((x) => x.default());
|
// Update meta cache every 5 minitues
|
||||||
// Update meta cache every 5 minitues
|
setInterval(() => updateMetaCache(), 1000 * 60 * 5);
|
||||||
setInterval(() => updateMetaCache(), 1000 * 60 * 5);
|
// Remove old attestation challenges
|
||||||
// Remove old attestation challenges
|
setInterval(() => removeOldAttestationChallenges(), 1000 * 60 * 30);
|
||||||
setInterval(() => removeOldAttestationChallenges(), 1000 * 60 * 30);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showEnvironment(): void {
|
function showEnvironment(): void {
|
||||||
|
|
|
@ -25,6 +25,6 @@ export async function workerMain() {
|
||||||
|
|
||||||
if (cluster.isWorker) {
|
if (cluster.isWorker) {
|
||||||
// Send a 'ready' message to parent process
|
// Send a 'ready' message to parent process
|
||||||
process.send!("ready");
|
process.send?.("ready");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { loadConfig, loadEnv } from "backend-rs";
|
import { loadConfig } from "backend-rs";
|
||||||
|
|
||||||
export const config = loadConfig();
|
export const config = loadConfig();
|
||||||
export const envOption = loadEnv();
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ export function toHtml(
|
||||||
},
|
},
|
||||||
|
|
||||||
fn(node) {
|
fn(node) {
|
||||||
const el = doc.createElement("i");
|
const el = doc.createElement("span");
|
||||||
appendChildren(node.children, el);
|
appendChildren(node.children, el);
|
||||||
return el;
|
return el;
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type httpSignature from "@peertube/http-signature";
|
import type httpSignature from "@peertube/http-signature";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
|
|
||||||
import { config, envOption } from "@/config.js";
|
import { config } from "@/config.js";
|
||||||
import type { DriveFile } from "@/models/entities/drive-file.js";
|
import type { DriveFile } from "@/models/entities/drive-file.js";
|
||||||
import type { IActivity } from "@/remote/activitypub/type.js";
|
import type { IActivity } from "@/remote/activitypub/type.js";
|
||||||
import type { Webhook, webhookEventTypes } from "@/models/entities/webhook.js";
|
import type { Webhook, webhookEventTypes } from "@/models/entities/webhook.js";
|
||||||
|
@ -518,8 +518,6 @@ export function webhookDeliver(
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
if (envOption.onlyServer) return;
|
|
||||||
|
|
||||||
deliverQueue.process(config.deliverJobConcurrency || 128, processDeliver);
|
deliverQueue.process(config.deliverJobConcurrency || 128, processDeliver);
|
||||||
inboxQueue.process(config.inboxJobConcurrency || 16, processInbox);
|
inboxQueue.process(config.inboxJobConcurrency || 16, processInbox);
|
||||||
endedPollNotificationQueue.process(endedPollNotification);
|
endedPollNotificationQueue.process(endedPollNotification);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { Meta } from "@/models/entities/meta.js";
|
||||||
import { insertModerationLog } from "@/services/insert-moderation-log.js";
|
import { insertModerationLog } from "@/services/insert-moderation-log.js";
|
||||||
import { db } from "@/db/postgre.js";
|
import { db } from "@/db/postgre.js";
|
||||||
import define from "@/server/api/define.js";
|
import define from "@/server/api/define.js";
|
||||||
|
import { updateMetaCache } from "backend-rs";
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ["admin"],
|
tags: ["admin"],
|
||||||
|
@ -583,5 +584,5 @@ export default define(meta, paramDef, async (ps, me) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
insertModerationLog(me, "updateMeta");
|
await Promise.all([insertModerationLog(me, "updateMeta"), updateMetaCache()]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import define from "@/server/api/define.js";
|
import define from "@/server/api/define.js";
|
||||||
import { fetchMeta, genIdAt } from "backend-rs";
|
import { fetchMeta, genIdAt, updateAntennaCache } from "backend-rs";
|
||||||
import { Antennas, UserLists, UserGroupJoinings } from "@/models/index.js";
|
import { Antennas, UserLists, UserGroupJoinings } from "@/models/index.js";
|
||||||
import { ApiError } from "@/server/api/error.js";
|
import { ApiError } from "@/server/api/error.js";
|
||||||
import { publishInternalEvent } from "@/services/stream.js";
|
import { publishInternalEvent } from "@/services/stream.js";
|
||||||
|
@ -173,6 +173,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
}).then((x) => Antennas.findOneByOrFail(x.identifiers[0]));
|
}).then((x) => Antennas.findOneByOrFail(x.identifiers[0]));
|
||||||
|
|
||||||
publishInternalEvent("antennaCreated", antenna);
|
publishInternalEvent("antennaCreated", antenna);
|
||||||
|
await updateAntennaCache();
|
||||||
|
|
||||||
return await Antennas.pack(antenna);
|
return await Antennas.pack(antenna);
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,6 +2,7 @@ import define from "@/server/api/define.js";
|
||||||
import { ApiError } from "@/server/api/error.js";
|
import { ApiError } from "@/server/api/error.js";
|
||||||
import { Antennas } from "@/models/index.js";
|
import { Antennas } from "@/models/index.js";
|
||||||
import { publishInternalEvent } from "@/services/stream.js";
|
import { publishInternalEvent } from "@/services/stream.js";
|
||||||
|
import { updateAntennaCache } from "backend-rs";
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ["antennas"],
|
tags: ["antennas"],
|
||||||
|
@ -40,4 +41,5 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
await Antennas.delete(antenna.id);
|
await Antennas.delete(antenna.id);
|
||||||
|
|
||||||
publishInternalEvent("antennaDeleted", antenna);
|
publishInternalEvent("antennaDeleted", antenna);
|
||||||
|
await updateAntennaCache();
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,6 +2,7 @@ import define from "@/server/api/define.js";
|
||||||
import { ApiError } from "@/server/api/error.js";
|
import { ApiError } from "@/server/api/error.js";
|
||||||
import { Antennas, UserLists, UserGroupJoinings } from "@/models/index.js";
|
import { Antennas, UserLists, UserGroupJoinings } from "@/models/index.js";
|
||||||
import { publishInternalEvent } from "@/services/stream.js";
|
import { publishInternalEvent } from "@/services/stream.js";
|
||||||
|
import { updateAntennaCache } from "backend-rs";
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ["antennas"],
|
tags: ["antennas"],
|
||||||
|
@ -166,6 +167,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
"antennaUpdated",
|
"antennaUpdated",
|
||||||
await Antennas.findOneByOrFail({ id: antenna.id }),
|
await Antennas.findOneByOrFail({ id: antenna.id }),
|
||||||
);
|
);
|
||||||
|
await updateAntennaCache();
|
||||||
|
|
||||||
return await Antennas.pack(antenna.id);
|
return await Antennas.pack(antenna.id);
|
||||||
});
|
});
|
||||||
|
|
|
@ -75,7 +75,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
||||||
const credentialIdLength = authData.readUInt16BE(53);
|
const credentialIdLength = authData.readUInt16BE(53);
|
||||||
const credentialId = authData.slice(55, 55 + credentialIdLength);
|
const credentialId = authData.slice(55, 55 + credentialIdLength);
|
||||||
const publicKeyData = authData.slice(55 + credentialIdLength);
|
const publicKeyData = authData.slice(55 + credentialIdLength);
|
||||||
const publicKey: Map<Number, any> = new Map(
|
const publicKey: Map<number, any> = new Map(
|
||||||
Object.entries(decode(publicKeyData)).map(([key, value]) => [
|
Object.entries(decode(publicKeyData)).map(([key, value]) => [
|
||||||
Number(key),
|
Number(key),
|
||||||
value,
|
value,
|
||||||
|
|
|
@ -27,9 +27,9 @@ export default async (ctx: Koa.Context) => {
|
||||||
ctx.set("Access-Control-Allow-Credentials", "true");
|
ctx.set("Access-Control-Allow-Credentials", "true");
|
||||||
|
|
||||||
const body = ctx.request.body as any;
|
const body = ctx.request.body as any;
|
||||||
const username = body["username"];
|
const username = body.username;
|
||||||
const password = body["password"];
|
const password = body.password;
|
||||||
const token = body["token"];
|
const token = body.token;
|
||||||
|
|
||||||
function error(status: number, error: { id: string }) {
|
function error(status: number, error: { id: string }) {
|
||||||
ctx.status = status;
|
ctx.status = status;
|
||||||
|
|
|
@ -10,10 +10,9 @@ import Router from "@koa/router";
|
||||||
import cors from "@koa/cors";
|
import cors from "@koa/cors";
|
||||||
import mount from "koa-mount";
|
import mount from "koa-mount";
|
||||||
import koaLogger from "koa-logger";
|
import koaLogger from "koa-logger";
|
||||||
import * as slow from "koa-slow";
|
|
||||||
|
|
||||||
import { IsNull } from "typeorm";
|
import { IsNull } from "typeorm";
|
||||||
import { config, envOption } from "@/config.js";
|
import { config } from "@/config.js";
|
||||||
import Logger from "@/services/logger.js";
|
import Logger from "@/services/logger.js";
|
||||||
import { Users } from "@/models/index.js";
|
import { Users } from "@/models/index.js";
|
||||||
import { fetchMeta, stringToAcct } from "backend-rs";
|
import { fetchMeta, stringToAcct } from "backend-rs";
|
||||||
|
@ -53,15 +52,6 @@ if (!["production", "test"].includes(process.env.NODE_ENV || "")) {
|
||||||
serverLogger.debug(str);
|
serverLogger.debug(str);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Delay
|
|
||||||
if (envOption.slow) {
|
|
||||||
app.use(
|
|
||||||
slow({
|
|
||||||
delay: 3000,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// HSTS
|
// HSTS
|
||||||
|
|
|
@ -546,7 +546,7 @@ router.get("/notes/:note", async (ctx, next) => {
|
||||||
ctx.set("Cache-Control", "public, max-age=15");
|
ctx.set("Cache-Control", "public, max-age=15");
|
||||||
ctx.set(
|
ctx.set(
|
||||||
"Content-Security-Policy",
|
"Content-Security-Policy",
|
||||||
"default-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src *; font-src 'self' data:; img-src *; media-src *; worker-src 'self'; frame-ancestors *",
|
"default-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src *; font-src 'self' data:; img-src * data:; media-src *; worker-src 'self'; frame-ancestors *",
|
||||||
);
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import cluster from "node:cluster";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { default as convertColor } from "color-convert";
|
import { default as convertColor } from "color-convert";
|
||||||
import { format as dateFormat } from "date-fns";
|
import { format as dateFormat } from "date-fns";
|
||||||
import { config, envOption } from "@/config.js";
|
import { config } from "@/config.js";
|
||||||
|
|
||||||
import * as SyslogPro from "syslog-pro";
|
import * as SyslogPro from "syslog-pro";
|
||||||
|
|
||||||
|
@ -133,7 +133,6 @@ export default class Logger {
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
let log = `${l} ${worker}\t[${domains.join(" ")}]\t${m}`;
|
let log = `${l} ${worker}\t[${domains.join(" ")}]\t${m}`;
|
||||||
if (envOption.withLogTime) log = `${chalk.gray(time)} ${log}`;
|
|
||||||
|
|
||||||
console.log(important ? chalk.bold(log) : log);
|
console.log(important ? chalk.bold(log) : log);
|
||||||
|
|
||||||
|
@ -212,8 +211,7 @@ export default class Logger {
|
||||||
// Fixed if statement is ignored when logLevel includes debug
|
// Fixed if statement is ignored when logLevel includes debug
|
||||||
if (
|
if (
|
||||||
config.logLevel?.includes("debug") ||
|
config.logLevel?.includes("debug") ||
|
||||||
process.env.NODE_ENV !== "production" ||
|
process.env.NODE_ENV !== "production"
|
||||||
envOption.verbose
|
|
||||||
) {
|
) {
|
||||||
this.log("debug", message, data, important);
|
this.log("debug", message, data, important);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,14 @@
|
||||||
:value="qrCode"
|
:value="qrCode"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<MkButton :class="$style.gotIt" primary full @click="gotIt()">{{
|
<div class="_flexList" style="gap: 0.6rem">
|
||||||
i18n.ts.gotIt
|
<MkButton :class="$style.gotIt" primary full @click="gotIt()">{{
|
||||||
}}</MkButton>
|
i18n.ts.gotIt
|
||||||
|
}}</MkButton>
|
||||||
|
<MkButton :class="$style.copyLink" full @click="copyLink()">{{
|
||||||
|
i18n.ts.copyLink
|
||||||
|
}}</MkButton>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkModal>
|
</MkModal>
|
||||||
</template>
|
</template>
|
||||||
|
@ -19,8 +24,10 @@ import QRCodeVue3 from "qrcode-vue3";
|
||||||
import MkModal from "@/components/MkModal.vue";
|
import MkModal from "@/components/MkModal.vue";
|
||||||
import MkButton from "@/components/MkButton.vue";
|
import MkButton from "@/components/MkButton.vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import * as os from "@/os";
|
||||||
|
import copyToClipboard from "@/scripts/copy-to-clipboard";
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
qrCode: string;
|
qrCode: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -29,6 +36,11 @@ const modal = shallowRef<InstanceType<typeof MkModal>>();
|
||||||
const gotIt = () => {
|
const gotIt = () => {
|
||||||
modal.value.close();
|
modal.value.close();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const copyLink = () => {
|
||||||
|
copyToClipboard(props.qrCode);
|
||||||
|
os.success();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
|
|
@ -399,6 +399,13 @@ export function getNoteMenu(props: {
|
||||||
action: copyOriginal,
|
action: copyOriginal,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
{
|
||||||
|
icon: `${icon("ph-qr-code")}`,
|
||||||
|
text: i18n.ts.getQrCode,
|
||||||
|
action: () => {
|
||||||
|
os.displayQrCode(`${url}/notes/${appearNote.id}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
shareAvailable()
|
shareAvailable()
|
||||||
? {
|
? {
|
||||||
icon: `${icon("ph-share-network")}`,
|
icon: `${icon("ph-share-network")}`,
|
||||||
|
@ -516,6 +523,13 @@ export function getNoteMenu(props: {
|
||||||
action: copyOriginal,
|
action: copyOriginal,
|
||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
|
{
|
||||||
|
icon: `${icon("ph-qr-code")}`,
|
||||||
|
text: i18n.ts.getQrCode,
|
||||||
|
action: () => {
|
||||||
|
os.displayQrCode(`${url}/notes/${appearNote.id}`);
|
||||||
|
},
|
||||||
|
},
|
||||||
shareAvailable()
|
shareAvailable()
|
||||||
? {
|
? {
|
||||||
icon: `${icon("ph-share-network")}`,
|
icon: `${icon("ph-share-network")}`,
|
||||||
|
|
|
@ -213,9 +213,6 @@ importers:
|
||||||
koa-send:
|
koa-send:
|
||||||
specifier: 5.0.1
|
specifier: 5.0.1
|
||||||
version: 5.0.1
|
version: 5.0.1
|
||||||
koa-slow:
|
|
||||||
specifier: 2.1.0
|
|
||||||
version: 2.1.0
|
|
||||||
mfm-js:
|
mfm-js:
|
||||||
specifier: 0.24.0
|
specifier: 0.24.0
|
||||||
version: 0.24.0
|
version: 0.24.0
|
||||||
|
@ -5419,10 +5416,6 @@ packages:
|
||||||
resolution: {integrity: sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==}
|
resolution: {integrity: sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
koa-slow@2.1.0:
|
|
||||||
resolution: {integrity: sha512-ii6s1zuZ51p+SY7WIrwjRi1tmPrNpeHEaw5UYi4h1QzAPmIcNk16e9zwKd9+eNNzI9n+Q2LXHAvt1MCfs7j/8w==}
|
|
||||||
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
|
||||||
|
|
||||||
koa-static@5.0.0:
|
koa-static@5.0.0:
|
||||||
resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==}
|
resolution: {integrity: sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==}
|
||||||
engines: {node: '>= 7.6.0'}
|
engines: {node: '>= 7.6.0'}
|
||||||
|
@ -5520,9 +5513,6 @@ packages:
|
||||||
lodash.isequal@4.5.0:
|
lodash.isequal@4.5.0:
|
||||||
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
|
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
|
||||||
|
|
||||||
lodash.isregexp@3.0.5:
|
|
||||||
resolution: {integrity: sha512-VlV0abdYZs5asSYW1JW5W1f6gxf2SGQt90rzQp7UNTQ8KwcB3CprZe5crN1LIlCA/fB5R9xecrZijGSELJL8Yg==}
|
|
||||||
|
|
||||||
lodash.map@4.6.0:
|
lodash.map@4.6.0:
|
||||||
resolution: {integrity: sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==}
|
resolution: {integrity: sha512-worNHGKLDetmcEYDvh2stPCrrQRkP20E4l0iIS7F8EvzMqBBi7ltvFN5m1HvTf1P7Jk1txKhvFcmYsCr8O2F1Q==}
|
||||||
|
|
||||||
|
@ -6398,10 +6388,6 @@ packages:
|
||||||
resolution: {integrity: sha512-P8aonTNAnXWJn2pBIqyeWw0I/D4YDOfEavCVvbDG+wx3dCujQX0ENZiW5OcHfbd8HKLfVhCf4F/3Xivf1yWDiA==}
|
resolution: {integrity: sha512-P8aonTNAnXWJn2pBIqyeWw0I/D4YDOfEavCVvbDG+wx3dCujQX0ENZiW5OcHfbd8HKLfVhCf4F/3Xivf1yWDiA==}
|
||||||
engines: {node: '>=14.19.0'}
|
engines: {node: '>=14.19.0'}
|
||||||
|
|
||||||
q@1.4.1:
|
|
||||||
resolution: {integrity: sha512-/CdEdaw49VZVmyIDGUQKDDT53c7qBkO6g5CefWz91Ae+l4+cRtcDYwMTXh6me4O8TMldeGHG3N2Bl84V78Ywbg==}
|
|
||||||
engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
|
|
||||||
|
|
||||||
qrcode-generator@1.4.4:
|
qrcode-generator@1.4.4:
|
||||||
resolution: {integrity: sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==}
|
resolution: {integrity: sha512-HM7yY8O2ilqhmULxGMpcHSF1EhJJ9yBj8gvDEuZ6M+KGJ0YY2hKpnXvRD+hZPLrDVck3ExIGhmPtSdcjC+guuw==}
|
||||||
|
|
||||||
|
@ -12997,11 +12983,6 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
koa-slow@2.1.0:
|
|
||||||
dependencies:
|
|
||||||
lodash.isregexp: 3.0.5
|
|
||||||
q: 1.4.1
|
|
||||||
|
|
||||||
koa-static@5.0.0:
|
koa-static@5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 3.2.7
|
debug: 3.2.7
|
||||||
|
@ -13190,8 +13171,6 @@ snapshots:
|
||||||
|
|
||||||
lodash.isequal@4.5.0: {}
|
lodash.isequal@4.5.0: {}
|
||||||
|
|
||||||
lodash.isregexp@3.0.5: {}
|
|
||||||
|
|
||||||
lodash.map@4.6.0: {}
|
lodash.map@4.6.0: {}
|
||||||
|
|
||||||
lodash.memoize@4.1.2: {}
|
lodash.memoize@4.1.2: {}
|
||||||
|
@ -14071,8 +14050,6 @@ snapshots:
|
||||||
opentype.js: 0.4.11
|
opentype.js: 0.4.11
|
||||||
pngjs: 7.0.0
|
pngjs: 7.0.0
|
||||||
|
|
||||||
q@1.4.1: {}
|
|
||||||
|
|
||||||
qrcode-generator@1.4.4: {}
|
qrcode-generator@1.4.4: {}
|
||||||
|
|
||||||
qrcode-vue3@1.6.8:
|
qrcode-vue3@1.6.8:
|
||||||
|
|
Loading…
Reference in a new issue