Compare commits
122 Commits
ed4d4e6897
...
2bfe7a15d5
Author | SHA1 | Date |
---|---|---|
vib | 2bfe7a15d5 | |
Norm | e58c940d6f | |
Johann150 | 08af6fda37 | |
Johann150 | 0c8a3cfeec | |
Johann150 | 8bc366fde0 | |
Johann150 | 417d252e9d | |
Johann150 | b54e07caec | |
Johann150 | e0560dbe9e | |
Johann150 | 5b898c6c82 | |
Johann150 | 6010884e62 | |
Johann150 | b423d23cf6 | |
Johann150 | 29714d1ae0 | |
Johann150 | 7bf4d4426a | |
Johann150 | d28931bf00 | |
Johann150 | 2a46719f31 | |
Johann150 | 7f564431be | |
Johann150 | 0fbd7fa492 | |
Johann150 | 3aaa9facc6 | |
Johann150 | 8f09b05e7c | |
Johann150 | 8b0b7ff525 | |
Johann150 | 0b7c9095bf | |
Johann150 | 338e898f56 | |
Puniko | 3a9d283630 | |
Johann150 | ed27f61a4d | |
Chloe Kudryavtsev | ed9d4023d4 | |
Johann150 | 76a8e000a3 | |
Johann150 | a673647fba | |
Johann150 | eea2eb4919 | |
Johann150 | 114d416de0 | |
Johann150 | c2372315f7 | |
Norm | 09bc3cf95a | |
Johann150 | de3cdb5833 | |
Norm | a732cdc1ad | |
Norm | a8f82050c8 | |
Norm | 8e12b9a33e | |
Norm | 6583d0c43d | |
Johann150 | c02a03168d | |
Johann150 | 85419326f8 | |
Johann150 | eaa11647f0 | |
Johann150 | 61a2db49df | |
Johann150 | 79ddbafd0d | |
Norm | 0e1459e5cf | |
Johann150 | e8e82dac82 | |
Norm | 9690244848 | |
Norm | 4db25e4b1f | |
Norm | 549302e9c0 | |
Norm | a3354904af | |
Norm | 28f65bebfc | |
Norm | 2204adc657 | |
Norm | b11e4053db | |
Johann150 | e2ef800708 | |
Johann150 | a7048f17f7 | |
Johann150 | ddf3e2c3db | |
Johann150 | 52afff800a | |
Johann150 | 33f0b24c56 | |
Andy | 7685b92511 | |
Andy | 8276bd3bdc | |
Andy | aed2752470 | |
Andy | 4a3b91d658 | |
Johann150 | 9317d25078 | |
Johann150 | fc36bb8880 | |
Johann150 | 711bb8be7d | |
Johann150 | 275136cf8b | |
Johann150 | aa33708b90 | |
Norm | d75e295ee8 | |
Norm | 766ab1c4c4 | |
Johann150 | 99c459a21a | |
Johann150 | bd68096ea9 | |
Johann150 | c411669133 | |
Johann150 | 639fa74d43 | |
Johann150 | 3bf7deb233 | |
Johann150 | 2520633210 | |
Johann150 | 4574db523a | |
Johann150 | 263fb94f3f | |
Johann150 | 0e3321c106 | |
Johann150 | 677ee537d1 | |
Johann150 | 1e539e6af5 | |
Johann150 | 0e5f744560 | |
Norm | 8f782f8ce5 | |
Johann150 | 6c7f1774e3 | |
Johann150 | af43df15ca | |
Johann150 | 5f83383ab8 | |
Johann150 | 8c759dde6c | |
Johann150 | 84d83d908a | |
Johann150 | 16d091497a | |
Johann150 | ef53ec276a | |
Johann150 | 3582fd8260 | |
Johann150 | 6256ddbd30 | |
Johann150 | 00fcc238f7 | |
Johann150 | 9f1670d5fd | |
Norm | ff31b8b06d | |
Johann150 | e317a771b3 | |
Johann150 | 398ee6435b | |
Johann150 | ffff2ae5ef | |
Johann150 | ccc8bf0289 | |
Johann150 | a231b36d59 | |
Johann150 | 8e9c65fab0 | |
Norm | 78a3051313 | |
Norm | 78717e85d3 | |
Norm | a9d3cae511 | |
Norm | bd27b7ca3a | |
Norm | e28a9eb8e8 | |
Norm | e5a4c5d2d0 | |
Norm | 6bba55c196 | |
Norm | 1d469f3c34 | |
Norm | 3f0228e14c | |
Johann150 | 73f81177b4 | |
Johann150 | 6a26da3516 | |
Johann150 | 5ea744b1b2 | |
Johann150 | ae6ba05306 | |
Sam Smucny | 21069223e3 | |
Johann150 | d4d1e03479 | |
Norm | 5513a3eb3a | |
Johann150 | d5dd7c1ef5 | |
Johann150 | a80521b6a8 | |
Norm | 030394b30d | |
Johann150 | 768d9bbdfb | |
Johann150 | 3ef1a4b0f9 | |
Johann150 | ae59ce51b0 | |
Johann150 | 14a9b9bedd | |
Johann150 | 985a13f47f | |
Norm | 3e46433ede |
|
@ -133,3 +133,10 @@ redis:
|
|||
#allowedPrivateNetworks: [
|
||||
# '127.0.0.1/32'
|
||||
#]
|
||||
|
||||
# images used on error screens. You can use absolute or relative URLs.
|
||||
# If you use relative URLs, be aware that the URL may be used on different pages/paths, so the path component should be absolute.
|
||||
#images:
|
||||
# info: /twemoji/1f440.svg
|
||||
# notFound: /twemoji/2049.svg
|
||||
# error: /twemoji/1f480.svg
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.autogen
|
||||
.vscode
|
||||
.config
|
||||
.woodpecker
|
||||
Dockerfile
|
||||
build/
|
||||
built/
|
||||
|
|
|
@ -2,9 +2,10 @@ root = true
|
|||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
indent_size = 4
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
|
|
@ -1,7 +1 @@
|
|||
*.svg -diff -text
|
||||
*.psd -diff -text
|
||||
*.ai -diff -text
|
||||
*.mqo -diff -text
|
||||
*.glb -diff -text
|
||||
*.blend -diff -text
|
||||
*.afdesign -diff -text
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Visual Studio Code
|
||||
/.vscode
|
||||
!/.vscode/extensions.json
|
||||
/.vsls.json
|
||||
|
||||
# Intelij-IDEA
|
||||
/.idea
|
||||
|
@ -11,6 +11,9 @@
|
|||
# nano
|
||||
.swp
|
||||
|
||||
# vimlocal
|
||||
.vimlocal
|
||||
|
||||
# Node.js
|
||||
node_modules
|
||||
report.*.json
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"editorconfig.editorconfig",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"Vue.volar",
|
||||
"Vue.vscode-typescript-vue-plugin"
|
||||
]
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"$schema": "http://json.schemastore.org/vsls",
|
||||
"gitignore": "exclude"
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
clone:
|
||||
git:
|
||||
image: woodpeckerci/plugin-git
|
||||
settings:
|
||||
depth: 1 # CI does not need commit history
|
||||
recursive: true
|
||||
|
||||
pipeline:
|
||||
install:
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
image: node:18.6.0
|
||||
commands:
|
||||
- yarn install
|
||||
lint:
|
||||
when:
|
||||
event:
|
||||
- pull_request
|
||||
image: node:18.6.0
|
||||
commands:
|
||||
- yarn workspace sw run lint
|
|
@ -316,8 +316,8 @@ This does not apply when using the Composition API since reactivation is manual.
|
|||
If you import json in TypeScript, the json file will be spit out together with the TypeScript file into the dist directory when compiling with tsc. This behavior may cause unintentional rewriting of files, so when importing json files, be sure to check whether the files are allowed to be rewritten or not. If you do not want the file to be rewritten, you should make sure that the file can be rewritten by importing the json file. If you do not want the file to be rewritten, use functions such as `fs.readFileSync` to read the file instead of importing it.
|
||||
|
||||
### Component style definitions do not have a `margin`
|
||||
Setting the `margin` of a component may be confusing.
|
||||
Instead, it should always be the user of a component that sets a `margin`.
|
||||
~~Setting the `margin` of a component may be confusing. Instead, it should always be the user of a component that sets a `margin`.~~
|
||||
This was a philosophy used previously. Hoever it now seems a better idea to add a default margin to the top level element of a component which can be easily overwritten on the usage of that component with a `style` attribute.
|
||||
|
||||
### Do not use the word "follow" in HTML class names
|
||||
This has caused things to be blocked by an ad blocker in the past.
|
||||
|
|
10
COPYING
|
@ -1,6 +1,6 @@
|
|||
Unless otherwise stated this repository is
|
||||
Copyright © 2014-2022 syuilo and contributors
|
||||
Copyright © 2022 FoundKey contributors
|
||||
Copyright © 2022-2023 FoundKey contributors
|
||||
And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE.
|
||||
(You may be able to run `git shortlog -se` to see a full list of authors.)
|
||||
|
||||
|
@ -13,3 +13,11 @@ https://github.com/muan/emojilib/blob/master/LICENSE
|
|||
RsaSignature2017 implementation by Transmute Industries Inc
|
||||
License: MIT
|
||||
https://github.com/transmute-industries/RsaSignature2017/blob/master/LICENSE
|
||||
|
||||
Chiptune2.js by Simon Gündling
|
||||
License: MIT
|
||||
https://github.com/deskjet/chiptune2.js#license
|
||||
|
||||
libopenmpt (as part of openmpt) by OpenMPT
|
||||
License: BSD 3-Clause
|
||||
https://github.com/OpenMPT/openmpt/blob/master/LICENSE
|
||||
|
|
Before Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 317 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 200 KiB |
BIN
assets/ai.png
Before Width: | Height: | Size: 235 KiB |
Before Width: | Height: | Size: 238 KiB |
Before Width: | Height: | Size: 148 KiB |
BIN
assets/title.png
Before Width: | Height: | Size: 3.8 KiB |
|
@ -36,7 +36,7 @@ gulp.task('copy:client:locales', cb => {
|
|||
});
|
||||
|
||||
gulp.task('build:backend:script', () => {
|
||||
return gulp.src(['./packages/backend/src/server/web/boot.js', './packages/backend/src/server/web/bios.js', './packages/backend/src/server/web/cli.js'])
|
||||
return gulp.src(['./packages/backend/src/server/web/boot.js'])
|
||||
.pipe(replace('LANGS', JSON.stringify(Object.keys(locales))))
|
||||
.pipe(terser({
|
||||
toplevel: true
|
||||
|
@ -45,7 +45,7 @@ gulp.task('build:backend:script', () => {
|
|||
});
|
||||
|
||||
gulp.task('build:backend:style', () => {
|
||||
return gulp.src(['./packages/backend/src/server/web/style.css', './packages/backend/src/server/web/bios.css', './packages/backend/src/server/web/cli.css'])
|
||||
return gulp.src(['./packages/backend/src/server/web/style.css'])
|
||||
.pipe(cssnano({
|
||||
zindex: false
|
||||
}))
|
||||
|
|
|
@ -96,6 +96,8 @@ unfollow: "Unfollow"
|
|||
followRequestPending: "Follow request pending"
|
||||
renote: "Renote"
|
||||
unrenote: "Take back renote"
|
||||
unrenoteAll: "Take back all renotes"
|
||||
unrenoteAllConfirm: "Are you sure that you want to take back all renotes of this note?"
|
||||
quote: "Quote"
|
||||
pinnedNote: "Pinned note"
|
||||
you: "You"
|
||||
|
@ -1095,6 +1097,7 @@ _permissions:
|
|||
"write:notes": "Create and delete notes"
|
||||
"read:notifications": "Read notifications"
|
||||
"write:notifications": "Mark notifications as read and create custom notifications"
|
||||
"read:reactions": "View reactions"
|
||||
"write:reactions": "Create and delete reactions"
|
||||
"write:votes": "Vote in polls"
|
||||
"read:pages": "List and read pages"
|
||||
|
|
11
package.json
|
@ -18,7 +18,7 @@
|
|||
"migrateandstart": "yarn migrate && yarn start",
|
||||
"gulp": "gulp build",
|
||||
"watch": "yarn dev",
|
||||
"dev": "node ./scripts/dev.js",
|
||||
"dev": "node ./scripts/dev.mjs",
|
||||
"lint": "yarn workspaces foreach run lint",
|
||||
"cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts",
|
||||
"cy:run": "cypress run",
|
||||
|
@ -26,8 +26,8 @@
|
|||
"mocha": "yarn workspace backend run mocha",
|
||||
"test": "yarn mocha",
|
||||
"format": "gulp format",
|
||||
"clean": "node ./scripts/clean.js",
|
||||
"clean-all": "node ./scripts/clean-all.js",
|
||||
"clean": "node ./scripts/clean.mjs",
|
||||
"clean-all": "node ./scripts/clean-all.mjs",
|
||||
"cleanall": "yarn clean-all"
|
||||
},
|
||||
"resolutions": {
|
||||
|
@ -35,6 +35,7 @@
|
|||
"lodash": "^4.17.21"
|
||||
},
|
||||
"dependencies": {
|
||||
"argon2": "^0.30.2",
|
||||
"execa": "5.1.1",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-cssnano": "2.1.3",
|
||||
|
@ -46,11 +47,11 @@
|
|||
"devDependencies": {
|
||||
"@types/gulp": "4.0.9",
|
||||
"@types/gulp-rename": "2.0.1",
|
||||
"@typescript-eslint/parser": "^5.44.0",
|
||||
"@typescript-eslint/parser": "^5.46.1",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "10.3.0",
|
||||
"start-server-and-test": "1.14.0",
|
||||
"typescript": "^4.9.3"
|
||||
"typescript": "^4.9.4"
|
||||
},
|
||||
"packageManager": "yarn@3.3.0"
|
||||
}
|
||||
|
|
|
@ -6,7 +6,11 @@ module.exports = {
|
|||
extends: [
|
||||
'../shared/.eslintrc.js',
|
||||
],
|
||||
plugins: [
|
||||
'foundkey-custom-rules',
|
||||
],
|
||||
rules: {
|
||||
'foundkey-custom-rules/typeorm-prefer-count': 'error',
|
||||
'import/order': ['warn', {
|
||||
'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
|
||||
'pathGroups': [
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
export class removeReversi1672607891750 {
|
||||
name = 'removeReversi1672607891750';
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`DROP TABLE "reversi_matching"`);
|
||||
await queryRunner.query(`DROP TABLE "reversi_game"`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`CREATE TABLE "reversi_game" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "startedAt" TIMESTAMP WITH TIME ZONE, "user1Id" character varying(32) NOT NULL, "user2Id" character varying(32) NOT NULL, "user1Accepted" boolean NOT NULL DEFAULT false, "user2Accepted" boolean NOT NULL DEFAULT false, "black" integer, "isStarted" boolean NOT NULL DEFAULT false, "isEnded" boolean NOT NULL DEFAULT false, "winnerId" character varying(32), "surrendered" character varying(32), "logs" jsonb NOT NULL DEFAULT '[]', "map" character varying(64) array NOT NULL, "bw" character varying(32) NOT NULL, "isLlotheo" boolean NOT NULL DEFAULT false, "canPutEverywhere" boolean NOT NULL DEFAULT false, "loopedBoard" boolean NOT NULL DEFAULT false, "form1" jsonb DEFAULT null, "form2" jsonb DEFAULT null, "crc32" character varying(32), CONSTRAINT "PK_76b30eeba71b1193ad7c5311c3f" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_b46ec40746efceac604142be1c" ON "reversi_game" ("createdAt")`);
|
||||
await queryRunner.query(`CREATE TABLE "reversi_matching" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "parentId" character varying(32) NOT NULL, "childId" character varying(32) NOT NULL, CONSTRAINT "PK_880bd0afbab232f21c8b9d146cf" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_b604d92d6c7aec38627f6eaf16" ON "reversi_matching" ("createdAt")`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_3b25402709dd9882048c2bbade" ON "reversi_matching" ("parentId")`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_e247b23a3c9b45f89ec1299d06" ON "reversi_matching" ("childId")`);
|
||||
await queryRunner.query(`ALTER TABLE "reversi_game" ADD CONSTRAINT "FK_f7467510c60a45ce5aca6292743" FOREIGN KEY ("user1Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "reversi_game" ADD CONSTRAINT "FK_6649a4e8c5d5cf32fb03b5da9f6" FOREIGN KEY ("user2Id") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "reversi_matching" ADD CONSTRAINT "FK_3b25402709dd9882048c2bbade0" FOREIGN KEY ("parentId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "reversi_matching" ADD CONSTRAINT "FK_e247b23a3c9b45f89ec1299d066" FOREIGN KEY ("childId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
"scripts": {
|
||||
"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
|
||||
"watch": "node watch.mjs",
|
||||
"lint": "eslint src --ext .ts",
|
||||
"lint": "tsc --noEmit && eslint src --ext .ts",
|
||||
"mocha": "cross-env NODE_ENV=test TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha",
|
||||
"migrate": "npx typeorm migration:run -d ormconfig.js",
|
||||
"start": "node --experimental-json-modules ./built/index.js",
|
||||
|
@ -166,14 +166,15 @@
|
|||
"@types/web-push": "3.3.2",
|
||||
"@types/websocket": "1.0.5",
|
||||
"@types/ws": "8.5.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.44.0",
|
||||
"@typescript-eslint/parser": "^5.44.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.46.1",
|
||||
"@typescript-eslint/parser": "^5.46.1",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "^8.29.0",
|
||||
"eslint-plugin-foundkey-custom-rules": "file:../shared/custom-rules",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"execa": "6.1.0",
|
||||
"form-data": "^4.0.0",
|
||||
"sinon": "^14.0.2",
|
||||
"typescript": "^4.9.3"
|
||||
"typescript": "^4.9.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ const ev = new Xev();
|
|||
/**
|
||||
* Init process
|
||||
*/
|
||||
export default async function(): Promise<void> {
|
||||
export async function boot(): Promise<void> {
|
||||
process.title = `FoundKey (${cluster.isPrimary ? 'master' : 'worker'})`;
|
||||
|
||||
if (cluster.isPrimary || envOption.disableClustering) {
|
||||
|
|
|
@ -140,7 +140,7 @@ async function connectDb(): Promise<void> {
|
|||
}
|
||||
|
||||
async function spawnWorkers(clusterLimits: Required<Config['clusterLimits']>): Promise<void> {
|
||||
const modes = ['web', 'queue'];
|
||||
const modes = ['web' as const, 'queue' as const];
|
||||
const cpus = os.cpus().length;
|
||||
for (const mode of modes.filter(mode => clusterLimits[mode] > cpus)) {
|
||||
bootLogger.warn(`configuration warning: cluster limit for ${mode} exceeds number of cores (${cpus})`);
|
||||
|
|
|
@ -38,6 +38,12 @@ export default function load(): Config {
|
|||
|
||||
config.port = config.port || parseInt(process.env.PORT || '', 10);
|
||||
|
||||
config.images = Object.assign({
|
||||
info: '/twemoji/1f440.svg',
|
||||
notFound: '/twemoji/2049.svg',
|
||||
error: '/twemoji/1f480.svg',
|
||||
}, config.images ?? {});
|
||||
|
||||
if (!config.maxNoteTextLength) config.maxNoteTextLength = 3000;
|
||||
|
||||
mixin.version = meta.version;
|
||||
|
|
|
@ -67,6 +67,12 @@ export type Source = {
|
|||
mediaProxy?: string;
|
||||
proxyRemoteFiles?: boolean;
|
||||
internalStoragePath?: string;
|
||||
|
||||
images?: {
|
||||
info?: string;
|
||||
notFound?: string;
|
||||
error?: string;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
// https://github.com/typeorm/typeorm/issues/2400
|
||||
import pg from 'pg';
|
||||
import { SECOND } from '@/const.js';
|
||||
|
||||
pg.types.setTypeParser(20, Number);
|
||||
|
||||
|
@ -8,6 +7,7 @@ import { Logger, DataSource } from 'typeorm';
|
|||
import * as highlight from 'cli-highlight';
|
||||
import config from '@/config/index.js';
|
||||
|
||||
import { SECOND } from '@/const.js';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { DriveFile } from '@/models/entities/drive-file.js';
|
||||
import { DriveFolder } from '@/models/entities/drive-folder.js';
|
||||
|
@ -78,33 +78,33 @@ import { redisClient } from './redis.js';
|
|||
const sqlLogger = dbLogger.createSubLogger('sql', 'gray', false);
|
||||
|
||||
class MyCustomLogger implements Logger {
|
||||
private highlight(sql: string) {
|
||||
private highlight(sql: string): string {
|
||||
return highlight.highlight(sql, {
|
||||
language: 'sql', ignoreIllegals: true,
|
||||
});
|
||||
}
|
||||
|
||||
public logQuery(query: string, parameters?: any[]) {
|
||||
public logQuery(query: string): void {
|
||||
sqlLogger.info(this.highlight(query).substring(0, 100));
|
||||
}
|
||||
|
||||
public logQueryError(error: string, query: string, parameters?: any[]) {
|
||||
public logQueryError(error: string, query: string): void {
|
||||
sqlLogger.error(this.highlight(query));
|
||||
}
|
||||
|
||||
public logQuerySlow(time: number, query: string, parameters?: any[]) {
|
||||
public logQuerySlow(time: number, query: string): void {
|
||||
sqlLogger.warn(this.highlight(query));
|
||||
}
|
||||
|
||||
public logSchemaBuild(message: string) {
|
||||
public logSchemaBuild(message: string): void {
|
||||
sqlLogger.info(message);
|
||||
}
|
||||
|
||||
public log(message: string) {
|
||||
public log(message: string): void {
|
||||
sqlLogger.info(message);
|
||||
}
|
||||
|
||||
public logMigration(message: string) {
|
||||
public logMigration(message: string): void {
|
||||
sqlLogger.info(message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
*/
|
||||
|
||||
import { EventEmitter } from 'node:events';
|
||||
import boot from '@/boot/index.js';
|
||||
import { boot } from '@/boot/index.js';
|
||||
|
||||
Error.stackTraceLimit = Infinity;
|
||||
EventEmitter.defaultMaxListeners = 128;
|
||||
|
|
|
@ -8,10 +8,7 @@ import { SECOND } from '@/const.js';
|
|||
*/
|
||||
const retryDelay = 100;
|
||||
|
||||
const lock: (key: string, timeout?: number) => Promise<() => void>
|
||||
= redisClient
|
||||
? promisify(redisLock(redisClient, retryDelay))
|
||||
: async () => () => { };
|
||||
const lock: (key: string, timeout?: number) => Promise<() => void> = promisify(redisLock(redisClient, retryDelay));
|
||||
|
||||
/**
|
||||
* Get AP Object lock
|
||||
|
|
|
@ -22,7 +22,7 @@ export async function checkHitAntenna(antenna: Antenna, note: (Note | Packed<'No
|
|||
if (note.visibility === 'specified') return false;
|
||||
|
||||
// skip if the antenna creator is blocked by the note author
|
||||
const blockings = await blockingCache.fetch(noteUser.id);
|
||||
const blockings = (await blockingCache.fetch(noteUser.id)) ?? [];
|
||||
if (blockings.some(blocking => blocking === antenna.userId)) return false;
|
||||
|
||||
if (note.visibility === 'followers') {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { db } from '@/db/postgre.js';
|
|||
import { Meta } from '@/models/entities/meta.js';
|
||||
import { getFetchInstanceMetadataLock } from '@/misc/app-lock.js';
|
||||
|
||||
let cache: Meta;
|
||||
let cache: Meta | undefined;
|
||||
|
||||
/**
|
||||
* Performs the primitive database operation to set the server configuration
|
||||
|
@ -57,5 +57,5 @@ export async function fetchMeta(noCache = false): Promise<Meta> {
|
|||
|
||||
await getMeta();
|
||||
|
||||
return cache;
|
||||
return cache!;
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ export async function getHtml(url: string, accept = 'text/html, */*', timeout =
|
|||
return await res.text();
|
||||
}
|
||||
|
||||
export async function getResponse(args: { url: string, method: string, body?: string, headers: Record<string, string>, timeout?: number, size?: number }) {
|
||||
export async function getResponse(args: { url: string, method: string, body?: string, headers: Record<string, string>, timeout?: number, size?: number, redirect: 'follow' | 'manual' | 'error' = 'follow' }) {
|
||||
const timeout = args.timeout || 10 * SECOND;
|
||||
|
||||
const controller = new AbortController();
|
||||
|
@ -47,8 +47,9 @@ export async function getResponse(args: { url: string, method: string, body?: st
|
|||
method: args.method,
|
||||
headers: args.headers,
|
||||
body: args.body,
|
||||
redirect: args.redirect,
|
||||
timeout,
|
||||
size: args.size || 10 * 1024 * 1024,
|
||||
size: args.size || 10 * 1024 * 1024, // 10 MiB
|
||||
agent: getAgentByUrl,
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
|
||||
export async function hashPassword(password: string): Promise<string> {
|
||||
return argon2.hash(password);
|
||||
}
|
||||
|
||||
export async function comparePassword(password: string, hash: string): Promise<boolean> {
|
||||
if (isOldAlgorithm(hash)) return bcrypt.compare(password, hash);
|
||||
|
||||
return argon2.verify(hash, password);
|
||||
}
|
||||
|
||||
export function isOldAlgorithm(hash: string): boolean {
|
||||
// bcrypt hashes start with $2[ab]$
|
||||
return hash.startsWith('$2');
|
||||
}
|
|
@ -75,7 +75,7 @@ export async function toDbReaction(reaction?: string | null, idnReacterHost?: st
|
|||
const custom = reaction.match(/^:([\w+-]+)(?:@\.)?:$/);
|
||||
if (custom) {
|
||||
const name = custom[1];
|
||||
const emoji = await Emojis.findOneBy({
|
||||
const emoji = await Emojis.countBy({
|
||||
host: reacterHost ?? IsNull(),
|
||||
name,
|
||||
});
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
import { Note } from '@/models/entities/note.js';
|
||||
|
||||
export function isPureRenote(note: Note): note is Note & { renoteId: string, text: null, fileIds: null | never[], hasPoll: false } {
|
||||
return note.renoteId != null && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !note.hasPoll;
|
||||
return note.renoteId != null
|
||||
&& note.text == null
|
||||
&& (
|
||||
note.fileIds == null
|
||||
|| note.fileIds.length === 0
|
||||
)
|
||||
&& !note.hasPoll;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as crypto from 'node:crypto';
|
||||
|
||||
const L_CHARS = '0123456789abcdefghijklmnopqrstuvwxyz';
|
||||
const LU_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
const LU_CHARS = L_CHARS + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
export function secureRndstrCustom(length = 32, chars: string): string {
|
||||
const chars_len = chars.length;
|
||||
|
|
|
@ -6,11 +6,10 @@ import { Meta } from '@/models/entities/meta.js';
|
|||
* Returns whether a specific host (punycoded) should be blocked.
|
||||
*
|
||||
* @param host punycoded instance host
|
||||
* @param meta a Promise contatining the information from the meta table (optional)
|
||||
* @param meta a resolved Meta table
|
||||
* @returns whether the given host should be blocked
|
||||
*/
|
||||
|
||||
export async function shouldBlockInstance(host: Instance['host'], meta: Promise<Meta> = fetchMeta()): Promise<boolean> {
|
||||
const { blockedHosts } = await meta;
|
||||
export async function shouldBlockInstance(host: Instance['host'], meta?: Meta): Promise<boolean> {
|
||||
const { blockedHosts } = meta ?? await fetchMeta();
|
||||
return blockedHosts.some(blockedHost => host === blockedHost || host.endsWith('.' + blockedHost));
|
||||
}
|
||||
|
|
|
@ -15,8 +15,8 @@ const deadThreshold = 7 * DAY;
|
|||
* @returns array of punycoded instance hosts that should be skipped (subset of hosts parameter)
|
||||
*/
|
||||
export async function skippedInstances(hosts: Array<Instance['host']>): Promise<Array<Instance['host']>> {
|
||||
// Resolve the boolean promises before filtering
|
||||
const meta = fetchMeta();
|
||||
// first check for blocked instances since that info may already be in memory
|
||||
const meta = await fetchMeta();
|
||||
const shouldSkip = await Promise.all(hosts.map(host => shouldBlockInstance(host, meta)));
|
||||
const skipped = hosts.filter((_, i) => shouldSkip[i]);
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ export class AbuseUserReport {
|
|||
@Column(id())
|
||||
public targetUserId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -27,7 +27,7 @@ export class AbuseUserReport {
|
|||
@Column(id())
|
||||
public reporterId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -39,7 +39,7 @@ export class AbuseUserReport {
|
|||
})
|
||||
public assigneeId: User['id'] | null;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -41,7 +41,7 @@ export class AccessToken {
|
|||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -53,7 +53,7 @@ export class AccessToken {
|
|||
})
|
||||
public appId: App['id'] | null;
|
||||
|
||||
@ManyToOne(type => App, {
|
||||
@ManyToOne(() => App, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -18,7 +18,7 @@ export class AnnouncementRead {
|
|||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -28,7 +28,7 @@ export class AnnouncementRead {
|
|||
@Column(id())
|
||||
public announcementId: Announcement['id'];
|
||||
|
||||
@ManyToOne(type => Announcement, {
|
||||
@ManyToOne(() => Announcement, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -32,12 +32,4 @@ export class Announcement {
|
|||
length: 1024, nullable: true,
|
||||
})
|
||||
public imageUrl: string | null;
|
||||
|
||||
constructor(data: Partial<Announcement>) {
|
||||
if (data == null) return;
|
||||
|
||||
for (const [k, v] of Object.entries(data)) {
|
||||
(this as any)[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ export class AntennaNote {
|
|||
})
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -29,7 +29,7 @@ export class AntennaNote {
|
|||
})
|
||||
public antennaId: Antenna['id'];
|
||||
|
||||
@ManyToOne(type => Antenna, {
|
||||
@ManyToOne(() => Antenna, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -21,7 +21,7 @@ export class Antenna {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -42,7 +42,7 @@ export class Antenna {
|
|||
})
|
||||
public userListId: UserList['id'] | null;
|
||||
|
||||
@ManyToOne(type => UserList, {
|
||||
@ManyToOne(() => UserList, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -54,7 +54,7 @@ export class Antenna {
|
|||
})
|
||||
public userGroupJoiningId: UserGroupJoining['id'] | null;
|
||||
|
||||
@ManyToOne(type => UserGroupJoining, {
|
||||
@ManyToOne(() => UserGroupJoining, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -21,7 +21,7 @@ export class App {
|
|||
})
|
||||
public userId: User['id'] | null;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'SET NULL',
|
||||
nullable: true,
|
||||
})
|
||||
|
|
|
@ -11,7 +11,7 @@ export class AttestationChallenge {
|
|||
@PrimaryColumn(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -35,12 +35,4 @@ export class AttestationChallenge {
|
|||
default: false,
|
||||
})
|
||||
public registrationChallenge: boolean;
|
||||
|
||||
constructor(data: Partial<AttestationChallenge>) {
|
||||
if (data == null) return;
|
||||
|
||||
for (const [k, v] of Object.entries(data)) {
|
||||
(this as any)[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Entity, PrimaryColumn, Index, Column, ManyToOne, OneToOne, JoinColumn } from 'typeorm';
|
||||
import { Entity, PrimaryColumn, Index, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { id } from '../id.js';
|
||||
import { AccessToken } from './access-token.js';
|
||||
import { App } from './app.js';
|
||||
|
|
|
@ -21,7 +21,7 @@ export class Blocking {
|
|||
})
|
||||
public blockeeId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -34,7 +34,7 @@ export class Blocking {
|
|||
})
|
||||
public blockerId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -22,7 +22,7 @@ export class ChannelFollowing {
|
|||
})
|
||||
public followeeId: Channel['id'];
|
||||
|
||||
@ManyToOne(type => Channel, {
|
||||
@ManyToOne(() => Channel, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -35,7 +35,7 @@ export class ChannelFollowing {
|
|||
})
|
||||
public followerId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -18,7 +18,7 @@ export class ChannelNotePining {
|
|||
@Column(id())
|
||||
public channelId: Channel['id'];
|
||||
|
||||
@ManyToOne(type => Channel, {
|
||||
@ManyToOne(() => Channel, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -27,7 +27,7 @@ export class ChannelNotePining {
|
|||
@Column(id())
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -28,7 +28,7 @@ export class Channel {
|
|||
})
|
||||
public userId: User['id'] | null;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -53,7 +53,7 @@ export class Channel {
|
|||
})
|
||||
public bannerId: DriveFile['id'] | null;
|
||||
|
||||
@ManyToOne(type => DriveFile, {
|
||||
@ManyToOne(() => DriveFile, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -16,7 +16,7 @@ export class ClipNote {
|
|||
})
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -29,7 +29,7 @@ export class ClipNote {
|
|||
})
|
||||
public clipId: Clip['id'];
|
||||
|
||||
@ManyToOne(type => Clip, {
|
||||
@ManyToOne(() => Clip, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -19,7 +19,7 @@ export class Clip {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -23,7 +23,7 @@ export class DriveFile {
|
|||
})
|
||||
public userId: User['id'] | null;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'RESTRICT',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -144,7 +144,7 @@ export class DriveFile {
|
|||
})
|
||||
public folderId: DriveFolder['id'] | null;
|
||||
|
||||
@ManyToOne(type => DriveFolder, {
|
||||
@ManyToOne(() => DriveFolder, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -27,7 +27,7 @@ export class DriveFolder {
|
|||
})
|
||||
public userId: User['id'] | null;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -41,7 +41,7 @@ export class DriveFolder {
|
|||
})
|
||||
public parentId: DriveFolder['id'] | null;
|
||||
|
||||
@ManyToOne(type => DriveFolder, {
|
||||
@ManyToOne(() => DriveFolder, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -20,7 +20,7 @@ export class FollowRequest {
|
|||
})
|
||||
public followeeId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -33,7 +33,7 @@ export class FollowRequest {
|
|||
})
|
||||
public followerId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -21,7 +21,7 @@ export class Following {
|
|||
})
|
||||
public followeeId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -34,7 +34,7 @@ export class Following {
|
|||
})
|
||||
public followerId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -16,7 +16,7 @@ export class GalleryLike {
|
|||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -25,7 +25,7 @@ export class GalleryLike {
|
|||
@Column(id())
|
||||
public postId: GalleryPost['id'];
|
||||
|
||||
@ManyToOne(type => GalleryPost, {
|
||||
@ManyToOne(() => GalleryPost, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -37,7 +37,7 @@ export class GalleryPost {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -22,7 +22,7 @@ export class MessagingMessage {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -35,7 +35,7 @@ export class MessagingMessage {
|
|||
})
|
||||
public recipientId: User['id'] | null;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -48,7 +48,7 @@ export class MessagingMessage {
|
|||
})
|
||||
public groupId: UserGroup['id'] | null;
|
||||
|
||||
@ManyToOne(type => UserGroup, {
|
||||
@ManyToOne(() => UserGroup, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -81,7 +81,7 @@ export class MessagingMessage {
|
|||
})
|
||||
public fileId: DriveFile['id'] | null;
|
||||
|
||||
@ManyToOne(type => DriveFile, {
|
||||
@ManyToOne(() => DriveFile, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -134,7 +134,7 @@ export class Meta {
|
|||
})
|
||||
public proxyAccountId: User['id'] | null;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -16,7 +16,7 @@ export class ModerationLog {
|
|||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -17,7 +17,7 @@ export class MutedNote {
|
|||
})
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -30,7 +30,7 @@ export class MutedNote {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -27,7 +27,7 @@ export class Muting {
|
|||
})
|
||||
public muteeId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -40,7 +40,7 @@ export class Muting {
|
|||
})
|
||||
public muterId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -18,7 +18,7 @@ export class NoteFavorite {
|
|||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -27,7 +27,7 @@ export class NoteFavorite {
|
|||
@Column(id())
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -19,7 +19,7 @@ export class NoteReaction {
|
|||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -29,7 +29,7 @@ export class NoteReaction {
|
|||
@Column(id())
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -2,7 +2,6 @@ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typ
|
|||
import { noteNotificationTypes } from 'foundkey-js';
|
||||
import { id } from '../id.js';
|
||||
import { User } from './user.js';
|
||||
import { Note } from './note.js';
|
||||
|
||||
@Entity()
|
||||
@Index(['userId', 'threadId'], { unique: true })
|
||||
|
@ -20,7 +19,7 @@ export class NoteThreadMuting {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -14,7 +14,7 @@ export class NoteUnread {
|
|||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -24,7 +24,7 @@ export class NoteUnread {
|
|||
@Column(id())
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -22,7 +22,7 @@ export class NoteWatching {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -35,7 +35,7 @@ export class NoteWatching {
|
|||
})
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -27,7 +27,7 @@ export class Note {
|
|||
})
|
||||
public replyId: Note['id'] | null;
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -41,7 +41,7 @@ export class Note {
|
|||
})
|
||||
public renoteId: Note['id'] | null;
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -75,7 +75,7 @@ export class Note {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -179,7 +179,7 @@ export class Note {
|
|||
})
|
||||
public channelId: Channel['id'] | null;
|
||||
|
||||
@ManyToOne(type => Channel, {
|
||||
@ManyToOne(() => Channel, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -28,7 +28,7 @@ export class Notification {
|
|||
})
|
||||
public notifieeId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -45,7 +45,7 @@ export class Notification {
|
|||
})
|
||||
public notifierId: User['id'] | null;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -89,7 +89,7 @@ export class Notification {
|
|||
})
|
||||
public noteId: Note['id'] | null;
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -101,7 +101,7 @@ export class Notification {
|
|||
})
|
||||
public followRequestId: FollowRequest['id'] | null;
|
||||
|
||||
@ManyToOne(type => FollowRequest, {
|
||||
@ManyToOne(() => FollowRequest, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -113,7 +113,7 @@ export class Notification {
|
|||
})
|
||||
public userGroupInvitationId: UserGroupInvitation['id'] | null;
|
||||
|
||||
@ManyToOne(type => UserGroupInvitation, {
|
||||
@ManyToOne(() => UserGroupInvitation, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -165,7 +165,7 @@ export class Notification {
|
|||
})
|
||||
public appAccessTokenId: AccessToken['id'] | null;
|
||||
|
||||
@ManyToOne(type => AccessToken, {
|
||||
@ManyToOne(() => AccessToken, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -16,7 +16,7 @@ export class PageLike {
|
|||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -25,7 +25,7 @@ export class PageLike {
|
|||
@Column(id())
|
||||
public pageId: Page['id'];
|
||||
|
||||
@ManyToOne(type => Page, {
|
||||
@ManyToOne(() => Page, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -57,7 +57,7 @@ export class Page {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -69,7 +69,7 @@ export class Page {
|
|||
})
|
||||
public eyeCatchingImageId: DriveFile['id'] | null;
|
||||
|
||||
@ManyToOne(type => DriveFile, {
|
||||
@ManyToOne(() => DriveFile, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -22,7 +22,7 @@ export class PasswordResetRequest {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -19,7 +19,7 @@ export class PollVote {
|
|||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -29,7 +29,7 @@ export class PollVote {
|
|||
@Column(id())
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -9,7 +9,7 @@ export class Poll {
|
|||
@PrimaryColumn(id())
|
||||
public noteId: Note['id'];
|
||||
|
||||
@OneToOne(type => Note, {
|
||||
@OneToOne(() => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -25,7 +25,7 @@ export class RegistryItem {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -21,7 +21,7 @@ export class RenoteMuting {
|
|||
})
|
||||
public muteeId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -34,7 +34,7 @@ export class RenoteMuting {
|
|||
})
|
||||
public muterId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -16,7 +16,7 @@ export class Signin {
|
|||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -14,7 +14,7 @@ export class SwSubscription {
|
|||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -21,7 +21,7 @@ export class UserGroupInvitation {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -34,7 +34,7 @@ export class UserGroupInvitation {
|
|||
})
|
||||
public userGroupId: UserGroup['id'];
|
||||
|
||||
@ManyToOne(type => UserGroup, {
|
||||
@ManyToOne(() => UserGroup, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -21,7 +21,7 @@ export class UserGroupJoining {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -34,7 +34,7 @@ export class UserGroupJoining {
|
|||
})
|
||||
public userGroupId: UserGroup['id'];
|
||||
|
||||
@ManyToOne(type => UserGroup, {
|
||||
@ManyToOne(() => UserGroup, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -25,7 +25,7 @@ export class UserGroup {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -7,7 +7,7 @@ export class UserKeypair {
|
|||
@PrimaryColumn(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@OneToOne(type => User, {
|
||||
@OneToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -21,7 +21,7 @@ export class UserListJoining {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -34,7 +34,7 @@ export class UserListJoining {
|
|||
})
|
||||
public userListId: UserList['id'];
|
||||
|
||||
@ManyToOne(type => UserList, {
|
||||
@ManyToOne(() => UserList, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -19,7 +19,7 @@ export class UserList {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -18,7 +18,7 @@ export class UserNotePining {
|
|||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -27,7 +27,7 @@ export class UserNotePining {
|
|||
@Column(id())
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
@ManyToOne(() => Note, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -11,7 +11,7 @@ export class UserProfile {
|
|||
@PrimaryColumn(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@OneToOne(type => User, {
|
||||
@OneToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -161,7 +161,7 @@ export class UserProfile {
|
|||
})
|
||||
public pinnedPageId: Page['id'] | null;
|
||||
|
||||
@OneToOne(type => Page, {
|
||||
@OneToOne(() => Page, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -7,7 +7,7 @@ export class UserPublickey {
|
|||
@PrimaryColumn(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@OneToOne(type => User, {
|
||||
@OneToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -13,7 +13,7 @@ export class UserSecurityKey {
|
|||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -81,7 +81,7 @@ export class User {
|
|||
})
|
||||
public avatarId: DriveFile['id'] | null;
|
||||
|
||||
@OneToOne(type => DriveFile, {
|
||||
@OneToOne(() => DriveFile, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
@ -94,7 +94,7 @@ export class User {
|
|||
})
|
||||
public bannerId: DriveFile['id'] | null;
|
||||
|
||||
@OneToOne(type => DriveFile, {
|
||||
@OneToOne(() => DriveFile, {
|
||||
onDelete: 'SET NULL',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -21,7 +21,7 @@ export class Webhook {
|
|||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
@ManyToOne(() => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
|
|
|
@ -27,9 +27,7 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
|
|||
|
||||
getPublicProperties(file: DriveFile): DriveFile['properties'] {
|
||||
if (file.properties.orientation != null) {
|
||||
// TODO
|
||||
//const properties = structuredClone(file.properties);
|
||||
const properties = JSON.parse(JSON.stringify(file.properties));
|
||||
const properties = structuredClone(file.properties);
|
||||
if (file.properties.orientation >= 5) {
|
||||
[properties.width, properties.height] = [properties.height, properties.width];
|
||||
}
|
||||
|
@ -63,50 +61,24 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
|
|||
return thumbnail ? (file.thumbnailUrl || (isImage ? (file.webpublicUrl || file.url) : null)) : (file.webpublicUrl || file.url);
|
||||
},
|
||||
|
||||
async calcDriveUsageOf(user: User['id'] | { id: User['id'] }): Promise<number> {
|
||||
const id = typeof user === 'object' ? user.id : user;
|
||||
|
||||
const { sum } = await this
|
||||
.createQueryBuilder('file')
|
||||
.where('file.userId = :id', { id })
|
||||
.andWhere('file.isLink = FALSE')
|
||||
.select('SUM(file.size)', 'sum')
|
||||
.getRawOne();
|
||||
|
||||
return parseInt(sum, 10) || 0;
|
||||
calcDriveUsageOf(id: User['id']): Promise<number> {
|
||||
return db.query('SELECT SUM(size) AS sum FROM drive_file WHERE "userId" = $1 AND NOT "isLink"', [id])
|
||||
.then(res => res[0].sum as number ?? 0);
|
||||
},
|
||||
|
||||
async calcDriveUsageOfHost(host: string): Promise<number> {
|
||||
const { sum } = await this
|
||||
.createQueryBuilder('file')
|
||||
.where('file.userHost = :host', { host: toPuny(host) })
|
||||
.andWhere('file.isLink = FALSE')
|
||||
.select('SUM(file.size)', 'sum')
|
||||
.getRawOne();
|
||||
|
||||
return parseInt(sum, 10) || 0;
|
||||
calcDriveUsageOfHost(host: string): Promise<number> {
|
||||
return db.query('SELECT SUM(size) AS sum FROM drive_file WHERE "userHost" = $1 AND NOT "isLink"', [toPuny(host)])
|
||||
.then(res => res[0].sum as number ?? 0);
|
||||
},
|
||||
|
||||
async calcDriveUsageOfLocal(): Promise<number> {
|
||||
const { sum } = await this
|
||||
.createQueryBuilder('file')
|
||||
.where('file.userHost IS NULL')
|
||||
.andWhere('file.isLink = FALSE')
|
||||
.select('SUM(file.size)', 'sum')
|
||||
.getRawOne();
|
||||
|
||||
return parseInt(sum, 10) || 0;
|
||||
calcDriveUsageOfLocal(): Promise<number> {
|
||||
return db.query('SELECT SUM(size) AS sum FROM drive_file WHERE "userHost" IS NULL AND NOT "isLink"')
|
||||
.then(res => res[0].sum as number ?? 0);
|
||||
},
|
||||
|
||||
async calcDriveUsageOfRemote(): Promise<number> {
|
||||
const { sum } = await this
|
||||
.createQueryBuilder('file')
|
||||
.where('file.userHost IS NOT NULL')
|
||||
.andWhere('file.isLink = FALSE')
|
||||
.select('SUM(file.size)', 'sum')
|
||||
.getRawOne();
|
||||
|
||||
return parseInt(sum, 10) || 0;
|
||||
calcDriveUsageOfRemote(): Promise<number> {
|
||||
return db.query('SELECT SUM(size) AS sum FROM drive_file WHERE "userHost" IS NOT NULL AND NOT "isLink"')
|
||||
.then(res => res[0].sum as number ?? 0);
|
||||
},
|
||||
|
||||
async pack(
|
||||
|
@ -154,26 +126,7 @@ export const DriveFileRepository = db.getRepository(DriveFile).extend({
|
|||
const file = typeof src === 'object' ? src : await this.findOneBy({ id: src });
|
||||
if (file == null) return null;
|
||||
|
||||
return await awaitAll<Packed<'DriveFile'>>({
|
||||
id: file.id,
|
||||
createdAt: file.createdAt.toISOString(),
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
md5: file.md5,
|
||||
size: file.size,
|
||||
isSensitive: file.isSensitive,
|
||||
blurhash: file.blurhash,
|
||||
properties: opts.self ? file.properties : this.getPublicProperties(file),
|
||||
url: opts.self ? file.url : this.getPublicUrl(file, false),
|
||||
thumbnailUrl: this.getPublicUrl(file, true),
|
||||
comment: file.comment,
|
||||
folderId: file.folderId,
|
||||
folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, {
|
||||
detail: true,
|
||||
}) : null,
|
||||
userId: opts.withUser ? file.userId : null,
|
||||
user: (opts.withUser && file.userId) ? Users.pack(file.userId) : null,
|
||||
});
|
||||
return await this.pack(file, opts);
|
||||
},
|
||||
|
||||
async packMany(
|
||||
|
|
|
@ -307,7 +307,6 @@ export const UserRepository = db.getRepository(User).extend({
|
|||
host: user.host,
|
||||
avatarUrl: this.getAvatarUrlSync(user),
|
||||
avatarBlurhash: user.avatar?.blurhash || null,
|
||||
avatarColor: null, // 後方互換性のため
|
||||
isAdmin: user.isAdmin || falsy,
|
||||
isModerator: user.isModerator || falsy,
|
||||
isBot: user.isBot || falsy,
|
||||
|
@ -332,7 +331,6 @@ export const UserRepository = db.getRepository(User).extend({
|
|||
lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null,
|
||||
bannerUrl: user.banner ? DriveFiles.getPublicUrl(user.banner, false) : null,
|
||||
bannerBlurhash: user.banner?.blurhash || null,
|
||||
bannerColor: null, // 後方互換性のため
|
||||
isLocked: user.isLocked,
|
||||
isSilenced: user.isSilenced || falsy,
|
||||
isSuspended: user.isSuspended || falsy,
|
||||
|
|