Merge branch 'refactor/system-user' into 'develop'

fix: correct user count


See merge request firefish/firefish!11124
This commit is contained in:
naskya 2024-07-10 15:26:10 +00:00
commit 277bffa08a
15 changed files with 68 additions and 54 deletions

View file

@ -1,6 +1,7 @@
BEGIN;
DELETE FROM "migrations" WHERE name IN (
'CreateSystemActors1720618854585',
'AddMastodonSubscriptionType1715181461692',
'SwSubscriptionAccessToken1709395223611',
'UserProfileMentions1711075007936',

View file

@ -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 {

View file

@ -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

View file

@ -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))

View file

@ -17,3 +17,4 @@ pub mod reaction;
pub mod remove_old_attestation_challenges;
pub mod should_nyaify;
pub mod system_info;
pub mod user;

View 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)
}

View file

@ -0,0 +1 @@
pub mod count;

View file

@ -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. */
}
}

View file

@ -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,
}),
);

View file

@ -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({

View file

@ -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,
}
: {}),
};

View file

@ -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(),
]);

View file

@ -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(),

View file

@ -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;
}
await cache.set(null, user);
return user;
}

View file

@ -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) {