mirror of
https://github.com/clusterio/clusterio.git
synced 2026-11-12 23:04:52 +09:00
Host not connected, host shutting down and instance not running are now UnavailableError (503) instead of ConflictError. The request error migration notes move to a new 2.1 guide, since this ships in 2.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
169 lines
4.8 KiB
TypeScript
169 lines
4.8 KiB
TypeScript
import { Static, Type } from "@sinclair/typebox";
|
|
import type { PermissionName } from "../permissions.js";
|
|
import PlayerStats from "./PlayerStats.js";
|
|
|
|
/** Represents a user in the cluster */
|
|
export default class UserDetails {
|
|
public playerStats: PlayerStats;
|
|
|
|
constructor(
|
|
/** Factorio user name. */
|
|
public name: string,
|
|
/** Roles this user has */
|
|
public roleIds = new Set<number>(),
|
|
/** Instances this user is online on. */
|
|
public instances = new Set<number>(),
|
|
/** True if the user is promoted to admin on the Factorio instances. */
|
|
public isAdmin = false,
|
|
/** True if the user is banned from Factorio instances. */
|
|
public isBanned = false,
|
|
/** True if the user is whitelisted on the Factorio instances. */
|
|
public isWhitelisted = false,
|
|
/** Reason for being banned. Ignored if isBanned is false. */
|
|
public banReason = "",
|
|
/** Millisecond Unix timestamp this entry was last updated at */
|
|
public updatedAtMs = 0,
|
|
/** True if this user object has been removed from the cluster. */
|
|
public isDeleted = false,
|
|
/** Per instance statistics for the player this user account is tied to. */
|
|
public instanceStats = new Map<number, PlayerStats>(),
|
|
) {
|
|
this.playerStats = UserDetails._calculatePlayerStats(this.instanceStats);
|
|
}
|
|
|
|
/** Name of the user, used by event subscriptions and to ensure case insensitivity */
|
|
get id() {
|
|
return this.name.toLowerCase();
|
|
}
|
|
|
|
static jsonSchema = Type.Object({
|
|
name: Type.String(),
|
|
roles: Type.Optional(Type.Array(Type.Integer())),
|
|
instances: Type.Optional(Type.Array(Type.Integer())),
|
|
is_admin: Type.Optional(Type.Boolean()),
|
|
is_banned: Type.Optional(Type.Boolean()),
|
|
is_whitelisted: Type.Optional(Type.Boolean()),
|
|
ban_reason: Type.Optional(Type.String()),
|
|
updated_at_ms: Type.Optional(Type.Number()),
|
|
is_deleted: Type.Optional(Type.Boolean()),
|
|
instance_stats: Type.Optional(
|
|
Type.Array(
|
|
Type.Tuple([Type.Integer(), PlayerStats.jsonSchema]),
|
|
)
|
|
),
|
|
});
|
|
|
|
static fromJSON(json: Static<typeof this.jsonSchema>) {
|
|
const instanceStats = new Map(
|
|
(json.instance_stats ?? []).map(([id, stats]) => [id, PlayerStats.fromJSON(stats)])
|
|
);
|
|
return new this(
|
|
json.name,
|
|
new Set<number>(json.roles),
|
|
new Set(json.instances),
|
|
json.is_admin,
|
|
json.is_banned,
|
|
json.is_whitelisted,
|
|
json.ban_reason,
|
|
json.updated_at_ms,
|
|
json.is_deleted,
|
|
instanceStats,
|
|
);
|
|
}
|
|
|
|
toJSON() {
|
|
let json: Static<typeof UserDetails.jsonSchema> = {
|
|
name: this.name,
|
|
};
|
|
|
|
if (this.roleIds.size) {
|
|
json.roles = [...this.roleIds];
|
|
}
|
|
|
|
if (this.instances.size) {
|
|
json.instances = [...this.instances];
|
|
}
|
|
|
|
if (this.isAdmin) {
|
|
json.is_admin = true;
|
|
}
|
|
|
|
if (this.isWhitelisted) {
|
|
json.is_whitelisted = true;
|
|
}
|
|
|
|
if (this.isBanned) {
|
|
json.is_banned = true;
|
|
}
|
|
|
|
if (this.banReason) {
|
|
json.ban_reason = this.banReason;
|
|
}
|
|
|
|
if (this.updatedAtMs) {
|
|
json.updated_at_ms = this.updatedAtMs;
|
|
}
|
|
|
|
if (this.isDeleted) {
|
|
json.is_deleted = this.isDeleted;
|
|
}
|
|
|
|
if (this.instanceStats.size) {
|
|
json.instance_stats = [...this.instanceStats].map(([id, stats]) => [id, stats.toJSON()]);
|
|
}
|
|
|
|
return json;
|
|
}
|
|
|
|
recalculatePlayerStats() {
|
|
this.playerStats = UserDetails._calculatePlayerStats(this.instanceStats);
|
|
}
|
|
|
|
static _calculatePlayerStats(instanceStatsMap: Map<number, PlayerStats>) {
|
|
let playerStats = new PlayerStats();
|
|
for (let instanceStats of instanceStatsMap.values()) {
|
|
if (
|
|
instanceStats.firstJoinAt
|
|
&& (!playerStats.firstJoinAt || instanceStats.firstJoinAt < playerStats.firstJoinAt)
|
|
) {
|
|
playerStats.firstJoinAt = instanceStats.firstJoinAt;
|
|
}
|
|
if (
|
|
instanceStats.lastJoinAt
|
|
&& (!playerStats.lastJoinAt || instanceStats.lastJoinAt > playerStats.lastJoinAt)
|
|
) {
|
|
playerStats.lastJoinAt = instanceStats.lastJoinAt;
|
|
}
|
|
if (
|
|
instanceStats.lastLeaveAt
|
|
&& (!playerStats.lastLeaveAt || instanceStats.lastLeaveAt > playerStats.lastLeaveAt)
|
|
) {
|
|
playerStats.lastLeaveAt = instanceStats.lastLeaveAt;
|
|
playerStats.lastLeaveReason = instanceStats.lastLeaveReason;
|
|
}
|
|
playerStats.joinCount += instanceStats.joinCount;
|
|
playerStats.onlineTimeMs += instanceStats.onlineTimeMs;
|
|
}
|
|
return playerStats;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This interface is implemented by User on the controller.
|
|
* It is exposed here to allow message definitions to create permission methods.
|
|
*/
|
|
export interface IUser extends UserDetails {
|
|
/**
|
|
* Check if a given permission is granted
|
|
*
|
|
* Checks the roles the user is member of for one that grants the given
|
|
* permission. If the permission is not granted for the user a
|
|
* "Permission denied" error is thrown.
|
|
*
|
|
* @param permission - The permission to check for.
|
|
* @throws {Error} If the given permission does not exist.
|
|
* @throws {libErrors.PermissionError} if the user does noh have the given permission.
|
|
*/
|
|
checkPermission(permission: PermissionName): void
|
|
}
|