diff --git a/.gitlab/issue_templates/bug.md b/.gitlab/issue_templates/bug.md
index a94fae0f0d..f96bdec01e 100644
--- a/.gitlab/issue_templates/bug.md
+++ b/.gitlab/issue_templates/bug.md
@@ -5,30 +5,39 @@
## What happened?
+
## What did you expect to happen?
+
## Version
+
## What type of issue is this?
-- [] server-side
-- [] client-side
-- [] not sure
+- [ ] server-side
+- [ ] client-side
+- [ ] not sure
### Instance
+
### What browser are you using? (client-side issues only)
+
### What operating system are you using? (client-side issues only)
+
### How do you deploy Firefish on your server? (server-side issues only)
+
### What operating system are you using? (Server-side issues only)
+
### Relevant log output
+
## Contribution Guidelines
diff --git a/.gitlab/issue_templates/feature.md b/.gitlab/issue_templates/feature.md
index 7b4917ecb3..4c9ee56226 100644
--- a/.gitlab/issue_templates/feature.md
+++ b/.gitlab/issue_templates/feature.md
@@ -5,12 +5,16 @@
## What feature would you like implemented?
+
## Why should we add this feature?
+
## Version
+
## Instance
+
## Contribution Guidelines
By submitting this issue, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md)
- [ ] I agree to follow this project's Contribution Guidelines
diff --git a/.gitlab/merge_request_templates/default.md b/.gitlab/merge_request_templates/default.md
index c2382481e3..d13a146da0 100644
--- a/.gitlab/merge_request_templates/default.md
+++ b/.gitlab/merge_request_templates/default.md
@@ -2,6 +2,7 @@
## What does this PR do?
+
## Contribution Guidelines
By submitting this merge request, you agree to follow our [Contribution Guidelines](https://firefish.dev/firefish/firefish/-/blob/develop/CONTRIBUTING.md)
- [ ] This change is reviewed in an issue / This is a minor bug fix
diff --git a/docker-compose.example.yml b/docker-compose.example.yml
index fc6c0268a2..9cd6d1cdce 100644
--- a/docker-compose.example.yml
+++ b/docker-compose.example.yml
@@ -17,6 +17,7 @@ services:
# - web
environment:
NODE_ENV: production
+ NODE_OPTIONS: --max-old-space-size=3072
volumes:
- ./custom:/firefish/custom:ro
- ./files:/firefish/files
diff --git a/docs/install.md b/docs/install.md
index 061000fa32..324923c6a7 100644
--- a/docs/install.md
+++ b/docs/install.md
@@ -154,7 +154,7 @@ sudo apt install ffmpeg
1. Build
```sh
pnpm install --frozen-lockfile
- NODE_ENV=production pnpm run build
+ NODE_ENV=production NODE_OPTIONS='--max-old-space-size=3072' pnpm run build
```
1. Execute database migrations
```sh
@@ -242,6 +242,7 @@ In this instruction, we use [Caddy](https://caddyserver.com/) to make the Firefi
WorkingDirectory=/home/firefish/firefish
Environment="NODE_ENV=production"
Environment="npm_config_cache=/tmp"
+ Environment="NODE_OPTIONS=--max-old-space-size=3072"
# uncomment the following line if you use jemalloc (note that the path varies on different environments)
# Environment="LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libjemalloc.so.2"
StandardOutput=journal
diff --git a/packages/backend-rs/index.d.ts b/packages/backend-rs/index.d.ts
index ae050e01d8..5ee69969c6 100644
--- a/packages/backend-rs/index.d.ts
+++ b/packages/backend-rs/index.d.ts
@@ -128,7 +128,8 @@ export interface Acct {
}
export function stringToAcct(acct: string): Acct
export function acctToString(acct: Acct): string
-export interface NoteLike {
+/** TODO: handle name collisions better */
+export interface NoteLikeForCheckWordMute {
fileIds: Array
userId: string | null
text: string | null
@@ -136,7 +137,7 @@ export interface NoteLike {
renoteId: string | null
replyId: string | null
}
-export function checkWordMute(note: NoteLike, mutedWordLists: Array>, mutedPatterns: Array): Promise
+export function checkWordMute(note: NoteLikeForCheckWordMute, mutedWordLists: Array>, mutedPatterns: Array): Promise
export function getFullApAccount(username: string, host?: string | undefined | null): string
export function isSelfHost(host?: string | undefined | null): boolean
export function isSameOrigin(uri: string): boolean
@@ -147,6 +148,14 @@ export function sqlLikeEscape(src: string): string
export function safeForSql(src: string): boolean
/** Convert milliseconds to a human readable string */
export function formatMilliseconds(milliseconds: number): string
+/** TODO: handle name collisions better */
+export interface NoteLikeForGetNoteSummary {
+ fileIds: Array
+ text: string | null
+ cw: string | null
+ hasPoll: boolean
+}
+export function getNoteSummary(note: NoteLikeForGetNoteSummary): string
export function toMastodonId(firefishId: string): string | null
export function fromMastodonId(mastodonId: string): string | null
export function fetchMeta(useCache: boolean): Promise
diff --git a/packages/backend-rs/index.js b/packages/backend-rs/index.js
index 6acd3ca68d..1ea7bb5bed 100644
--- a/packages/backend-rs/index.js
+++ b/packages/backend-rs/index.js
@@ -310,7 +310,7 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}
-const { readEnvironmentConfig, readServerConfig, stringToAcct, acctToString, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, AntennaSrcEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initIdGenerator, getTimestamp, genId, secureRndstr } = nativeBinding
+const { readEnvironmentConfig, readServerConfig, stringToAcct, acctToString, checkWordMute, getFullApAccount, isSelfHost, isSameOrigin, extractHost, toPuny, isUnicodeEmoji, sqlLikeEscape, safeForSql, formatMilliseconds, getNoteSummary, toMastodonId, fromMastodonId, fetchMeta, metaToPugArgs, nyaify, hashPassword, verifyPassword, isOldPasswordAlgorithm, decodeReaction, countReactions, toDbReaction, AntennaSrcEnum, MutedNoteReasonEnum, NoteVisibilityEnum, NotificationTypeEnum, PageVisibilityEnum, PollNotevisibilityEnum, RelayStatusEnum, UserEmojimodpermEnum, UserProfileFfvisibilityEnum, UserProfileMutingnotificationtypesEnum, initIdGenerator, getTimestamp, genId, secureRndstr } = nativeBinding
module.exports.readEnvironmentConfig = readEnvironmentConfig
module.exports.readServerConfig = readServerConfig
@@ -326,6 +326,7 @@ module.exports.isUnicodeEmoji = isUnicodeEmoji
module.exports.sqlLikeEscape = sqlLikeEscape
module.exports.safeForSql = safeForSql
module.exports.formatMilliseconds = formatMilliseconds
+module.exports.getNoteSummary = getNoteSummary
module.exports.toMastodonId = toMastodonId
module.exports.fromMastodonId = fromMastodonId
module.exports.fetchMeta = fetchMeta
diff --git a/packages/backend-rs/src/misc/check_word_mute.rs b/packages/backend-rs/src/misc/check_word_mute.rs
index 801175c2af..18b550c29b 100644
--- a/packages/backend-rs/src/misc/check_word_mute.rs
+++ b/packages/backend-rs/src/misc/check_word_mute.rs
@@ -4,7 +4,8 @@ use once_cell::sync::Lazy;
use regex::Regex;
use sea_orm::{prelude::*, QuerySelect};
-#[crate::export(object)]
+/// TODO: handle name collisions better
+#[crate::export(object, js_name = "NoteLikeForCheckWordMute")]
pub struct NoteLike {
pub file_ids: Vec,
pub user_id: Option,
diff --git a/packages/backend-rs/src/misc/get_note_summary.rs b/packages/backend-rs/src/misc/get_note_summary.rs
new file mode 100644
index 0000000000..3b759b04f5
--- /dev/null
+++ b/packages/backend-rs/src/misc/get_note_summary.rs
@@ -0,0 +1,90 @@
+/// TODO: handle name collisions better
+#[crate::export(object, js_name = "NoteLikeForGetNoteSummary")]
+pub struct NoteLike {
+ pub file_ids: Vec,
+ pub text: Option,
+ pub cw: Option,
+ pub has_poll: bool,
+}
+
+#[crate::export]
+pub fn get_note_summary(note: NoteLike) -> String {
+ let mut buf: Vec = vec![];
+
+ if let Some(cw) = note.cw {
+ buf.push(cw)
+ } else if let Some(text) = note.text {
+ buf.push(text)
+ }
+
+ match note.file_ids.len() {
+ 0 => (),
+ 1 => buf.push("📎".to_string()),
+ n => buf.push(format!("📎 ({})", n)),
+ };
+
+ if note.has_poll {
+ buf.push("📊".to_string())
+ }
+
+ buf.join(" ")
+}
+
+#[cfg(test)]
+mod unit_test {
+ use super::{get_note_summary, NoteLike};
+ use pretty_assertions::assert_eq;
+
+ #[test]
+ fn test_note_summary() {
+ let note = NoteLike {
+ file_ids: vec![],
+ text: Some("Hello world!".to_string()),
+ cw: None,
+ has_poll: false,
+ };
+ assert_eq!(get_note_summary(note), "Hello world!");
+
+ let note_with_cw = NoteLike {
+ file_ids: vec![],
+ text: Some("Hello world!".to_string()),
+ cw: Some("Content warning".to_string()),
+ has_poll: false,
+ };
+ assert_eq!(get_note_summary(note_with_cw), "Content warning");
+
+ let note_with_file_and_cw = NoteLike {
+ file_ids: vec!["9s7fmcqogiq4igin".to_string()],
+ text: None,
+ cw: Some("Selfie, no ec".to_string()),
+ has_poll: false,
+ };
+ assert_eq!(get_note_summary(note_with_file_and_cw), "Selfie, no ec 📎");
+
+ let note_with_files_only = NoteLike {
+ file_ids: vec![
+ "9s7fmcqogiq4igin".to_string(),
+ "9s7qrld5u14cey98".to_string(),
+ "9s7gebs5zgts4kca".to_string(),
+ "9s5z3e4vefqd29ee".to_string(),
+ ],
+ text: None,
+ cw: None,
+ has_poll: false,
+ };
+ assert_eq!(get_note_summary(note_with_files_only), "📎 (4)");
+
+ let note_all = NoteLike {
+ file_ids: vec![
+ "9s7fmcqogiq4igin".to_string(),
+ "9s7qrld5u14cey98".to_string(),
+ "9s7gebs5zgts4kca".to_string(),
+ "9s5z3e4vefqd29ee".to_string(),
+ ],
+ text: Some("Hello world!".to_string()),
+ cw: Some("Content warning".to_string()),
+ has_poll: true,
+ };
+ assert_eq!(get_note_summary(note_all), "Content warning 📎 (4) 📊");
+ }
+}
diff --git a/packages/backend-rs/src/misc/mod.rs b/packages/backend-rs/src/misc/mod.rs
index 45fd31cdcd..a9d7074dbf 100644
--- a/packages/backend-rs/src/misc/mod.rs
+++ b/packages/backend-rs/src/misc/mod.rs
@@ -4,6 +4,7 @@ pub mod convert_host;
pub mod emoji;
pub mod escape_sql;
pub mod format_milliseconds;
+pub mod get_note_summary;
pub mod mastodon_id;
pub mod meta;
pub mod nyaify;
diff --git a/packages/backend/src/daemons/server-stats.ts b/packages/backend/src/daemons/server-stats.ts
index 92265ecba7..df1f9b3032 100644
--- a/packages/backend/src/daemons/server-stats.ts
+++ b/packages/backend/src/daemons/server-stats.ts
@@ -13,16 +13,15 @@ const round = (num: number) => Math.round(num * 10) / 10;
/**
* Report server stats regularly
*/
-export default function () {
+export default async function () {
const log = [] as any[];
ev.on("requestServerStatsLog", (x) => {
ev.emit(`serverStatsLog:${x.id}`, log.slice(0, x.length || 50));
});
- fetchMeta(true).then((meta) => {
- if (!meta.enableServerMachineStats) return;
- });
+ const meta = await fetchMeta(true);
+ if (!meta.enableServerMachineStats) return;
async function tick() {
const cpu = await cpuUsage();
diff --git a/packages/backend/src/misc/get-note-summary.ts b/packages/backend/src/misc/get-note-summary.ts
deleted file mode 100644
index 0a662e434e..0000000000
--- a/packages/backend/src/misc/get-note-summary.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import type { Packed } from "./schema.js";
-
-/**
- * 投稿を表す文字列を取得します。
- * @param {*} note (packされた)投稿
- */
-export const getNoteSummary = (note: Packed<"Note">): string => {
- if (note.deletedAt) {
- return "❌";
- }
-
- let summary = "";
-
- // 本文
- if (note.cw != null) {
- summary += note.cw;
- } else {
- summary += note.text ? note.text : "";
- }
-
- // ファイルが添付されているとき
- if ((note.files || []).length !== 0) {
- const len = note.files?.length;
- summary += ` 📎${len !== 1 ? ` (${len})` : ""}`;
- }
-
- // 投票が添付されているとき
- if (note.poll) {
- summary += " 📊";
- }
-
- /*
- // 返信のとき
- if (note.replyId) {
- if (note.reply) {
- summary += `\n\nRE: ${getNoteSummary(note.reply)}`;
- } else {
- summary += '\n\nRE: ...';
- }
- }
-
- // Renoteのとき
- if (note.renoteId) {
- if (note.renote) {
- summary += `\n\nRN: ${getNoteSummary(note.renote)}`;
- } else {
- summary += '\n\nRN: ...';
- }
- }
- */
-
- return summary.trim();
-};
diff --git a/packages/backend/src/models/schema/note.ts b/packages/backend/src/models/schema/note.ts
index 7dcdbc9b03..fff872b69f 100644
--- a/packages/backend/src/models/schema/note.ts
+++ b/packages/backend/src/models/schema/note.ts
@@ -28,7 +28,7 @@ export const packedNoteSchema = {
},
cw: {
type: "string",
- optional: true,
+ optional: false,
nullable: true,
},
userId: {
@@ -98,7 +98,7 @@ export const packedNoteSchema = {
},
fileIds: {
type: "array",
- optional: true,
+ optional: false,
nullable: false,
items: {
type: "string",
@@ -128,6 +128,11 @@ export const packedNoteSchema = {
nullable: false,
},
},
+ hasPoll: {
+ type: "boolean",
+ optional: false,
+ nullable: false,
+ },
poll: {
type: "object",
optional: true,
diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts
index 325b54f350..65241becae 100644
--- a/packages/backend/src/server/api/endpoints/pinned-users.ts
+++ b/packages/backend/src/server/api/endpoints/pinned-users.ts
@@ -44,9 +44,7 @@ export default define(meta, paramDef, async (ps, me) => {
),
);
- return await Users.packMany(
- users.filter((x) => x !== undefined) as User[],
- me,
- { detail: true },
- );
+ return await Users.packMany(users.filter((x) => x != null) as User[], me, {
+ detail: true,
+ });
});
diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts
index 6473073370..939fcfab14 100644
--- a/packages/backend/src/server/web/index.ts
+++ b/packages/backend/src/server/web/index.ts
@@ -27,8 +27,7 @@ import {
Emojis,
GalleryPosts,
} from "@/models/index.js";
-import { stringToAcct } from "backend-rs";
-import { getNoteSummary } from "@/misc/get-note-summary.js";
+import { getNoteSummary, stringToAcct } from "backend-rs";
import { queues } from "@/queue/queues.js";
import { genOpenapiSpec } from "../api/openapi/gen-spec.js";
import { urlPreviewHandler } from "./url-preview.js";
@@ -517,8 +516,8 @@ router.get("/notes/:note", async (ctx, next) => {
});
try {
- if (note) {
- const _note = await Notes.pack(note);
+ if (note != null) {
+ const packedNote = await Notes.pack(note);
const profile = await UserProfiles.findOneByOrFail({
userId: note.userId,
@@ -526,13 +525,13 @@ router.get("/notes/:note", async (ctx, next) => {
const meta = await fetchMeta(true);
await ctx.render("note", {
...metaToPugArgs(meta),
- note: _note,
+ note: packedNote,
profile,
avatarUrl: await Users.getAvatarUrl(
await Users.findOneByOrFail({ id: note.userId }),
),
// TODO: Let locale changeable by instance setting
- summary: getNoteSummary(_note),
+ summary: getNoteSummary(note),
});
ctx.set("Cache-Control", "public, max-age=15");
diff --git a/packages/backend/src/services/push-notification.ts b/packages/backend/src/services/push-notification.ts
index 1a772ff9c5..3f1f2cfb1a 100644
--- a/packages/backend/src/services/push-notification.ts
+++ b/packages/backend/src/services/push-notification.ts
@@ -1,9 +1,8 @@
import push from "web-push";
import config from "@/config/index.js";
import { SwSubscriptions } from "@/models/index.js";
-import { fetchMeta } from "backend-rs";
+import { fetchMeta, getNoteSummary } from "backend-rs";
import type { Packed } from "@/misc/schema.js";
-import { getNoteSummary } from "@/misc/get-note-summary.js";
// Defined also packages/sw/types.ts#L14-L21
type pushNotificationsTypes = {
@@ -17,15 +16,15 @@ type pushNotificationsTypes = {
// プッシュメッセージサーバーには文字数制限があるため、内容を削減します
function truncateNotification(notification: Packed<"Notification">): any {
- if (notification.note) {
+ if (notification.note != null) {
return {
...notification,
note: {
...notification.note,
- // textをgetNoteSummaryしたものに置き換える
+ // replace the text with summary
text: getNoteSummary(
- notification.type === "renote"
- ? (notification.note.renote as Packed<"Note">)
+ notification.type === "renote" && notification.note.renote != null
+ ? notification.note.renote
: notification.note,
),