Merge branch 'refactor/system-user' into 'develop'
fix: correct user count See merge request firefish/firefish!11124
This commit is contained in:
commit
277bffa08a
15 changed files with 68 additions and 54 deletions
|
@ -1,6 +1,7 @@
|
|||
BEGIN;
|
||||
|
||||
DELETE FROM "migrations" WHERE name IN (
|
||||
'CreateSystemActors1720618854585',
|
||||
'AddMastodonSubscriptionType1715181461692',
|
||||
'SwSubscriptionAccessToken1709395223611',
|
||||
'UserProfileMentions1711075007936',
|
||||
|
|
2
packages/backend-rs/index.d.ts
vendored
2
packages/backend-rs/index.d.ts
vendored
|
@ -257,6 +257,8 @@ export interface Config {
|
|||
userAgent: string
|
||||
}
|
||||
|
||||
export declare function countLocalUsers(): Promise<number>
|
||||
|
||||
export declare function countReactions(reactions: Record<string, number>): Record<string, number>
|
||||
|
||||
export interface Cpu {
|
||||
|
|
|
@ -366,6 +366,7 @@ module.exports.AntennaSrc = nativeBinding.AntennaSrc
|
|||
module.exports.ChatEvent = nativeBinding.ChatEvent
|
||||
module.exports.ChatIndexEvent = nativeBinding.ChatIndexEvent
|
||||
module.exports.checkWordMute = nativeBinding.checkWordMute
|
||||
module.exports.countLocalUsers = nativeBinding.countLocalUsers
|
||||
module.exports.countReactions = nativeBinding.countReactions
|
||||
module.exports.cpuInfo = nativeBinding.cpuInfo
|
||||
module.exports.cpuUsage = nativeBinding.cpuUsage
|
||||
|
|
|
@ -4,6 +4,7 @@ use crate::{
|
|||
config::{local_server_info, CONFIG},
|
||||
database::db_conn,
|
||||
federation::nodeinfo::schema::*,
|
||||
misc,
|
||||
model::entity::{note, user},
|
||||
};
|
||||
use sea_orm::prelude::*;
|
||||
|
@ -33,9 +34,7 @@ async fn statistics() -> Result<(u64, u64, u64, u64), DbErr> {
|
|||
const MONTH: chrono::TimeDelta = chrono::Duration::days(30);
|
||||
const HALF_YEAR: chrono::TimeDelta = chrono::Duration::days(183);
|
||||
|
||||
let local_users = user::Entity::find()
|
||||
.filter(user::Column::Host.is_null())
|
||||
.count(db);
|
||||
let local_users = misc::user::count::local_total();
|
||||
let local_active_halfyear = user::Entity::find()
|
||||
.filter(user::Column::Host.is_null())
|
||||
.filter(user::Column::LastActiveDate.gt(now - HALF_YEAR))
|
||||
|
|
|
@ -17,3 +17,4 @@ pub mod reaction;
|
|||
pub mod remove_old_attestation_challenges;
|
||||
pub mod should_nyaify;
|
||||
pub mod system_info;
|
||||
pub mod user;
|
||||
|
|
18
packages/backend-rs/src/misc/user/count.rs
Normal file
18
packages/backend-rs/src/misc/user/count.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use crate::{database::db_conn, model::entity::user};
|
||||
use sea_orm::prelude::*;
|
||||
|
||||
// @instance.actor and @relay.actor are not real users
|
||||
const NUMBER_OF_SYSTEM_ACTORS: u64 = 2;
|
||||
|
||||
pub async fn local_total() -> Result<u64, DbErr> {
|
||||
user::Entity::find()
|
||||
.filter(user::Column::Host.is_null())
|
||||
.count(db_conn().await?)
|
||||
.await
|
||||
.map(|count| count - NUMBER_OF_SYSTEM_ACTORS)
|
||||
}
|
||||
|
||||
#[macros::ts_export(js_name = "countLocalUsers")]
|
||||
pub async fn local_total_js() -> Result<u32, DbErr> {
|
||||
local_total().await.map(|count| count as u32)
|
||||
}
|
1
packages/backend-rs/src/misc/user/mod.rs
Normal file
1
packages/backend-rs/src/misc/user/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod count;
|
|
@ -1,3 +1,5 @@
|
|||
import type { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
import { v4 as uuid } from "uuid";
|
||||
import { genRsaKeyPair } from "@/misc/gen-key-pair.js";
|
||||
import { User } from "@/models/entities/user.js";
|
||||
|
@ -9,7 +11,7 @@ import { UserKeypair } from "@/models/entities/user-keypair.js";
|
|||
import { UsedUsername } from "@/models/entities/used-username.js";
|
||||
import { db } from "@/db/postgre.js";
|
||||
|
||||
export async function createSystemUser(username: string) {
|
||||
async function createSystemUser(username: string) {
|
||||
const password = uuid();
|
||||
|
||||
// Generate hash of password
|
||||
|
@ -20,22 +22,20 @@ export async function createSystemUser(username: string) {
|
|||
|
||||
const keyPair = await genRsaKeyPair(4096);
|
||||
|
||||
let account!: User;
|
||||
|
||||
const exists = await Users.existsBy({
|
||||
usernameLower: username.toLowerCase(),
|
||||
host: IsNull(),
|
||||
});
|
||||
|
||||
if (exists) {
|
||||
throw new Error("the user already exists");
|
||||
return;
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
|
||||
// Start transaction
|
||||
await db.transaction(async (transactionalEntityManager) => {
|
||||
account = await transactionalEntityManager
|
||||
const account = await transactionalEntityManager
|
||||
.insert(User, {
|
||||
id: genIdAt(now),
|
||||
createdAt: now,
|
||||
|
@ -69,6 +69,18 @@ export async function createSystemUser(username: string) {
|
|||
username: username.toLowerCase(),
|
||||
});
|
||||
});
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
export class CreateSystemActors1720618854585 implements MigrationInterface {
|
||||
public async up(_: QueryRunner): Promise<void> {
|
||||
if (!db.isInitialized) {
|
||||
db.initialize();
|
||||
}
|
||||
await createSystemUser("instance.actor");
|
||||
await createSystemUser("relay.actor");
|
||||
}
|
||||
|
||||
public async down(_: QueryRunner): Promise<void> {
|
||||
/* You don't need to revert this migration. */
|
||||
}
|
||||
}
|
|
@ -3,7 +3,13 @@ import { User } from "@/models/entities/user.js";
|
|||
import { Users, UsedUsernames } from "@/models/index.js";
|
||||
import { UserProfile } from "@/models/entities/user-profile.js";
|
||||
import { IsNull } from "typeorm";
|
||||
import { genIdAt, generateUserToken, hashPassword, toPuny } from "backend-rs";
|
||||
import {
|
||||
countLocalUsers,
|
||||
genIdAt,
|
||||
generateUserToken,
|
||||
hashPassword,
|
||||
toPuny,
|
||||
} from "backend-rs";
|
||||
import { UserKeypair } from "@/models/entities/user-keypair.js";
|
||||
import { UsedUsername } from "@/models/entities/used-username.js";
|
||||
import { db } from "@/db/postgre.js";
|
||||
|
@ -18,9 +24,7 @@ export async function signup(opts: {
|
|||
const { username, password, passwordHash, host } = opts;
|
||||
let hash = passwordHash;
|
||||
|
||||
const userCount = await Users.countBy({
|
||||
host: IsNull(),
|
||||
});
|
||||
const userCount = await countLocalUsers();
|
||||
|
||||
if (config.maxUserSignups != null && userCount > config.maxUserSignups) {
|
||||
throw new Error("MAX_USERS_REACHED");
|
||||
|
@ -103,11 +107,7 @@ export async function signup(opts: {
|
|||
usernameLower: username.toLowerCase(),
|
||||
host: host == null ? null : toPuny(host),
|
||||
token: secret,
|
||||
isAdmin:
|
||||
(await Users.countBy({
|
||||
host: IsNull(),
|
||||
isAdmin: true,
|
||||
})) === 0,
|
||||
isAdmin: (await countLocalUsers()) === 0,
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import define from "@/server/api/define.js";
|
||||
import { Users } from "@/models/index.js";
|
||||
import { signup } from "@/server/api/common/signup.js";
|
||||
import { IsNull } from "typeorm";
|
||||
import { countLocalUsers } from "backend-rs";
|
||||
|
||||
export const meta = {
|
||||
tags: ["admin"],
|
||||
|
@ -32,11 +32,8 @@ export const paramDef = {
|
|||
|
||||
export default define(meta, paramDef, async (ps, _me, token) => {
|
||||
const me = _me ? await Users.findOneByOrFail({ id: _me.id }) : null;
|
||||
const noUsers =
|
||||
(await Users.countBy({
|
||||
host: IsNull(),
|
||||
})) === 0;
|
||||
if (!noUsers && !me?.isAdmin) throw new Error("access denied");
|
||||
if (!me?.isAdmin && (await countLocalUsers()) !== 0)
|
||||
throw new Error("access denied");
|
||||
if (token) throw new Error("access denied");
|
||||
|
||||
const { account, secret } = await signup({
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import JSON5 from "json5";
|
||||
import { IsNull, MoreThan } from "typeorm";
|
||||
import { config } from "@/config.js";
|
||||
import { fetchMeta } from "backend-rs";
|
||||
import { countLocalUsers, fetchMeta } from "backend-rs";
|
||||
import { Ads, Emojis, Users } from "@/models/index.js";
|
||||
import define from "@/server/api/define.js";
|
||||
|
||||
|
@ -501,11 +501,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
instance.privateMode && !me ? [] : instance.pinnedClipId,
|
||||
cacheRemoteFiles: instance.cacheRemoteFiles,
|
||||
markLocalFilesNsfwByDefault: instance.markLocalFilesNsfwByDefault,
|
||||
requireSetup:
|
||||
(await Users.countBy({
|
||||
host: IsNull(),
|
||||
isAdmin: true,
|
||||
})) === 0,
|
||||
requireSetup: (await countLocalUsers()) === 0,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Instances, Users, Notes } from "@/models/index.js";
|
||||
import define from "@/server/api/define.js";
|
||||
import { IsNull } from "typeorm";
|
||||
import { countLocalUsers } from "backend-rs";
|
||||
|
||||
export const meta = {
|
||||
requireCredential: false,
|
||||
|
@ -69,11 +70,7 @@ export default define(meta, paramDef, async () => {
|
|||
// usersCount
|
||||
Users.count(),
|
||||
// originalUsersCount
|
||||
Users.count({
|
||||
where: {
|
||||
host: IsNull(),
|
||||
},
|
||||
}),
|
||||
countLocalUsers(),
|
||||
// instances
|
||||
Instances.count(),
|
||||
]);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { config } from "@/config.js";
|
||||
import { FILE_TYPE_BROWSERSAFE } from "backend-rs";
|
||||
import { fetchMeta } from "backend-rs";
|
||||
import { countLocalUsers, fetchMeta } from "backend-rs";
|
||||
import {
|
||||
AnnouncementReads,
|
||||
Announcements,
|
||||
|
@ -31,7 +31,7 @@ export class MiscHelpers {
|
|||
public static async getInstance(
|
||||
ctx: MastoContext,
|
||||
): Promise<MastodonEntity.Instance> {
|
||||
const userCount = Users.count({ where: { host: IsNull() } });
|
||||
const userCount = countLocalUsers();
|
||||
const noteCount = Notes.count({ where: { userHost: IsNull() } });
|
||||
const instanceCount = Instances.count({ cache: 3600000 });
|
||||
const contact = await Users.findOne({
|
||||
|
@ -109,7 +109,7 @@ export class MiscHelpers {
|
|||
public static async getInstanceV2(
|
||||
ctx: MastoContext,
|
||||
): Promise<MastodonEntity.InstanceV2> {
|
||||
const userCount = await Users.count({ where: { host: IsNull() } });
|
||||
const userCount = await countLocalUsers();
|
||||
const contact = await Users.findOne({
|
||||
where: {
|
||||
host: IsNull(),
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { createSystemUser } from "./create-system-user.js";
|
||||
import type { ILocalUser } from "@/models/entities/user.js";
|
||||
import { Users } from "@/models/index.js";
|
||||
import { Cache } from "@/misc/cache.js";
|
||||
|
@ -15,14 +14,8 @@ export async function getInstanceActor(): Promise<ILocalUser> {
|
|||
const user = (await Users.findOneBy({
|
||||
host: IsNull(),
|
||||
username: ACTOR_USERNAME,
|
||||
})) as ILocalUser | undefined;
|
||||
})) as ILocalUser;
|
||||
|
||||
if (user) {
|
||||
await cache.set(null, user);
|
||||
return user;
|
||||
} else {
|
||||
const created = (await createSystemUser(ACTOR_USERNAME)) as ILocalUser;
|
||||
await cache.set(null, created);
|
||||
return created;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ import { Users, Relays } from "@/models/index.js";
|
|||
import { genId } from "backend-rs";
|
||||
import { Cache } from "@/misc/cache.js";
|
||||
import type { Relay } from "@/models/entities/relay.js";
|
||||
import { createSystemUser } from "@/services/create-system-user.js";
|
||||
|
||||
const ACTOR_USERNAME = "relay.actor" as const;
|
||||
|
||||
|
@ -23,10 +22,7 @@ export async function getRelayActor(): Promise<ILocalUser> {
|
|||
username: ACTOR_USERNAME,
|
||||
});
|
||||
|
||||
if (user) return user as ILocalUser;
|
||||
|
||||
const created = await createSystemUser(ACTOR_USERNAME);
|
||||
return created as ILocalUser;
|
||||
return user as ILocalUser;
|
||||
}
|
||||
|
||||
export async function addRelay(inbox: string) {
|
||||
|
|
Loading…
Reference in a new issue