feat: hosting access tab (#5995)

* feat: implement access tab with dummy data

* fix: spacing

* feat: qa

* feat: implement backend

* qa: qa pass

* feat: fix user "search"

* fix: lint

* feat: change to bitfield

* feat: fix fields

* fix: lint

* fix: lint

* feat: hook up api

* feat: fix permissions

* feat: audit log table event start

* feat: better mobile mode for audit log table

* feat: i18n

* feat: qa

* feat: enforce permissions

* feat: email template start

* feat: qa

* fix: tooltip bug

* feat: qa

* impl: sse support in api-client

* feat: sse impl

* fix: desync path

* feat: time frame picker from analytics

* feat: QA

* fix: spacing

* fix: permisison audit log entries

* fix: hosting manage page shared server detection

* fix: lint

* feat: qa + lint

* feat: audit log table sort by time

* feat: finish frontend panel stuff

* fix: lint

* fix: backend alignment

* fix: lint

* fix: supress friend errors

* feat: qa

* fix: qa

* fix: lint

* fix: utils barrel

* fix: safari cookies in dev

* fix: pin nuxt

* feat: fixes + notif fix

* fix: notifications

* feat: qa

* fix: notification sync not happening immediately

* fix: qa

* fix: qa

* feat: qa

* blog + prepr

* feat: toast shit

* blog images

* thumbnail update one last time

* prepr

* feat: use reinvite route

* update images

* fix: reinvite stuff

* fix: lint

* fix: alignment of save bar

* fix: notif sizing

* fix: split up access

* fix: lint

* fix: lint

* fix: link

---------

Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
Calum H.
2026-06-04 15:58:01 +00:00
committed by GitHub
co-authored by Prospector
parent 58ad58f958
commit bd97ace974
227 changed files with 15578 additions and 2153 deletions
@@ -0,0 +1,35 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Archon } from '../types'
export class ArchonActionsV1Module extends AbstractModule {
public getModuleID(): string {
return 'archon_actions_v1'
}
/**
* Get server action log entries.
* GET /v1/servers/:server_id/action-log
*/
public async list(
serverId: string,
options: Archon.Actions.v1.ListActionLogOptions = {},
): Promise<Archon.Actions.v1.ActionLogResponse> {
const params: Record<string, string | number> = {}
if (options.filter) params.filter = JSON.stringify(options.filter)
if (options.limit !== undefined) params.limit = options.limit
if (options.offset !== undefined) params.offset = options.offset
if (options.order !== undefined) params.order = options.order
if (options.min_datetime !== undefined) params.min_datetime = options.min_datetime
if (options.max_datetime !== undefined) params.max_datetime = options.max_datetime
return this.client.request<Archon.Actions.v1.ActionLogResponse>(
`/servers/${serverId}/action-log`,
{
api: 'archon',
version: 1,
method: 'GET',
params: Object.keys(params).length > 0 ? params : undefined,
},
)
}
}
@@ -1,3 +1,4 @@
export * from './actions/v1'
export * from './backups/v1'
export * from './backups-queue/v1'
export * from './content/v1'
@@ -0,0 +1,20 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Archon } from '../types'
export class ArchonNodesInternalModule extends AbstractModule {
public getModuleID(): string {
return 'archon_nodes_internal'
}
/**
* Get node hostnames and region summary for admin tooling.
* GET /_internal/nodes/overview
*/
public async overview(): Promise<Archon.Nodes.Internal.Overview> {
return this.client.request<Archon.Nodes.Internal.Overview>('/nodes/overview', {
api: 'archon',
version: 'internal',
method: 'GET',
})
}
}
@@ -0,0 +1,98 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Archon } from '../types'
export class ArchonNoticesV0Module extends AbstractModule {
public getModuleID(): string {
return 'archon_notices_v0'
}
/**
* Get all server notices.
* GET /modrinth/v0/notices
*/
public async list(): Promise<Archon.Notices.v0.ListedNotice[]> {
return this.client.request<Archon.Notices.v0.ListedNotice[]>('/notices', {
api: 'archon',
version: 'modrinth/v0',
method: 'GET',
})
}
/**
* Create a server notice.
* POST /modrinth/v0/notices
*/
public async create(
request: Archon.Notices.v0.Announce,
): Promise<Archon.Notices.v0.PostNoticeResponseBody> {
return this.client.request<Archon.Notices.v0.PostNoticeResponseBody>('/notices', {
api: 'archon',
version: 'modrinth/v0',
method: 'POST',
body: request,
})
}
/**
* Update a server notice.
* PATCH /modrinth/v0/notices/:id
*/
public async update(id: number, request: Archon.Notices.v0.AnnouncePatch): Promise<void> {
await this.client.request(`/notices/${id}`, {
api: 'archon',
version: 'modrinth/v0',
method: 'PATCH',
body: request,
})
}
/**
* Delete a server notice.
* DELETE /modrinth/v0/notices/:id
*/
public async delete(id: number): Promise<void> {
await this.client.request(`/notices/${id}`, {
api: 'archon',
version: 'modrinth/v0',
method: 'DELETE',
})
}
/**
* Assign a notice to a server or node.
* PUT /modrinth/v0/notices/:id/assign?server=:serverId
* PUT /modrinth/v0/notices/:id/assign?node=:nodeId
*/
public async assign(id: number, target: Archon.Notices.v0.AssignmentTarget): Promise<void> {
await this.client.request(`/notices/${id}/assign`, {
api: 'archon',
version: 'modrinth/v0',
method: 'PUT',
params: this.assignmentTargetToParams(target),
})
}
/**
* Unassign a notice from a server or node.
* PUT /modrinth/v0/notices/:id/unassign?server=:serverId
* PUT /modrinth/v0/notices/:id/unassign?node=:nodeId
*/
public async unassign(id: number, target: Archon.Notices.v0.AssignmentTarget): Promise<void> {
await this.client.request(`/notices/${id}/unassign`, {
api: 'archon',
version: 'modrinth/v0',
method: 'PUT',
params: this.assignmentTargetToParams(target),
})
}
private assignmentTargetToParams(
target: Archon.Notices.v0.AssignmentTarget,
): Record<string, string> {
if ('server' in target) {
return { server: target.server }
}
return { node: target.node }
}
}
@@ -0,0 +1,83 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Archon } from '../types'
export class ArchonServerUsersV1Module extends AbstractModule {
public getModuleID(): string {
return 'archon_server_users_v1'
}
/**
* Get list of users with access to a server
* GET /v1/servers/:server_id/users
*/
public async list(serverId: string): Promise<Archon.ServerUsers.v1.ServerUser[]> {
return this.client.request<Archon.ServerUsers.v1.ServerUser[]>(`/servers/${serverId}/users`, {
api: 'archon',
version: 1,
method: 'GET',
})
}
/**
* Add a user to a server
* POST /v1/servers/:server_id/users
*/
public async add(
serverId: string,
user: Archon.ServerUsers.v1.AddServerUserRequest,
): Promise<void> {
await this.client.request(`/servers/${serverId}/users`, {
api: 'archon',
version: 1,
method: 'POST',
body: user,
})
}
/**
* Re-send an invite to a pending server user.
* POST /v1/servers/:server_id/users/:user_id/reinvite
*/
public async reinvite(
serverId: string,
userId: string,
): Promise<Archon.ServerUsers.v1.ReinviteResponse> {
return this.client.request<Archon.ServerUsers.v1.ReinviteResponse>(
`/servers/${serverId}/users/${userId}/reinvite`,
{
api: 'archon',
version: 1,
method: 'POST',
},
)
}
/**
* Remove a user from a server
* DELETE /v1/servers/:server_id/users/:user_id
*/
public async delete(serverId: string, userId: string): Promise<void> {
await this.client.request(`/servers/${serverId}/users/${userId}`, {
api: 'archon',
version: 1,
method: 'DELETE',
})
}
/**
* Update a user's server role
* PATCH /v1/servers/:server_id/users/:user_id
*/
public async update(
serverId: string,
userId: string,
role: Archon.ServerUsers.v1.AssignableServerUserRole,
): Promise<void> {
await this.client.request(`/servers/${serverId}/users/${userId}`, {
api: 'archon',
version: 1,
method: 'PATCH',
body: JSON.stringify(role),
})
}
}
@@ -0,0 +1,84 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Archon } from '../types'
export class ArchonTransfersInternalModule extends AbstractModule {
public getModuleID(): string {
return 'archon_transfers_internal'
}
/**
* Schedule transfers for specific servers.
* POST /_internal/transfers/schedule/servers
*/
public async scheduleServers(
request: Archon.Transfers.Internal.ScheduleServerTransfersRequest,
): Promise<Archon.Transfers.Internal.ScheduleTransfersResponse> {
return this.client.request<Archon.Transfers.Internal.ScheduleTransfersResponse>(
'/transfers/schedule/servers',
{
api: 'archon',
version: 'internal',
method: 'POST',
body: request,
},
)
}
/**
* Schedule transfers for all servers on specific nodes.
* POST /_internal/transfers/schedule/nodes
*/
public async scheduleNodes(
request: Archon.Transfers.Internal.ScheduleNodeTransfersRequest,
): Promise<Archon.Transfers.Internal.ScheduleTransfersResponse> {
return this.client.request<Archon.Transfers.Internal.ScheduleTransfersResponse>(
'/transfers/schedule/nodes',
{
api: 'archon',
version: 'internal',
method: 'POST',
body: request,
},
)
}
/**
* Get transfer batch history.
* GET /_internal/transfers/history
*/
public async history(
options?: Archon.Transfers.Internal.TransferHistoryQuery,
): Promise<Archon.Transfers.Internal.TransferHistoryResponse> {
const params: Record<string, number> = {}
if (options?.page !== undefined) params.page = options.page
if (options?.page_size !== undefined) params.page_size = options.page_size
return this.client.request<Archon.Transfers.Internal.TransferHistoryResponse>(
'/transfers/history',
{
api: 'archon',
version: 'internal',
method: 'GET',
params,
},
)
}
/**
* Cancel pending transfer batches.
* POST /_internal/transfers/cancel
*/
public async cancel(
request: Archon.Transfers.Internal.CancelTransfersRequest,
): Promise<Archon.Transfers.Internal.CancelTransfersResponse> {
return this.client.request<Archon.Transfers.Internal.CancelTransfersResponse>(
'/transfers/cancel',
{
api: 'archon',
version: 'internal',
method: 'POST',
body: request,
},
)
}
}
+449 -4
View File
@@ -1,6 +1,287 @@
import type { Labrinth } from '../labrinth/types'
export namespace Archon {
export namespace Nodes {
export namespace Internal {
export type Node = {
id: string
hostname: string
region: string
created_at: string | null
locked: boolean
}
export type Server = {
id: string
available: boolean
}
export type NodeFull = Node & {
servers: Server[]
}
export type Overview = {
node_hostnames: string[]
regions: Region[]
total_servers_active: number
}
export type Region = {
display_name: string
country_code: string
key: string
server_count: number
node_count: number
}
export type RegionWithStatistics = {
region: Region
active_servers: string[]
}
}
}
export namespace Notices {
export namespace v0 {
export type Notice = {
id: number
dismissable: boolean
title: string | null
message: string
level: string
announced: string
}
export type ListedNotice = {
id: number
dismissable: boolean
message: string
title: string | null
level: string
announce_at: string
expires: string | null
assigned: Assignment[]
dismissed_by: Dismisser[]
}
export type Dismisser = {
server: string
dismissed_on: string
}
export type Assignment = {
kind: string
id: string
name: string
}
export type AssignmentTarget = { server: string } | { node: string }
export type Announce = {
message: string
title?: string | null
level: string
dismissable: boolean
announce_at: string
expires?: string | null
}
export type AnnouncePatch = {
message?: string
title?: string | null
level?: string
dismissable?: boolean
announce_at?: string
expires?: string | null
}
export type PostNoticeResponseBody = {
id: number
}
}
}
export namespace Actions {
export namespace v1 {
export type SortOrder = 'asc' | 'desc'
export type ActionName =
| 'server_created'
| 'changed_server_name'
| 'changed_server_subdomain'
| 'server_reallocated'
| 'server_plan_changed'
| 'user_invited'
| 'user_invite_revoked'
| 'user_permission_modified'
| 'user_removed'
| 'addon_added'
| 'addon_uploaded'
| 'addon_disabled'
| 'addon_enabled'
| 'addon_deleted'
| 'addon_updated'
| 'modpack_changed'
| 'modpack_unlinked'
| 'server_repaired'
| 'server_reset'
| 'server_started'
| 'server_stopped'
| 'server_restarted'
| 'server_killed'
| 'port_allocation_added'
| 'port_allocation_removed'
| 'loader_version_edited'
| 'game_version_edited'
| 'server_properties_modified'
| 'file_uploaded'
| 'file_deleted'
| 'file_renamed'
| 'file_edited'
| 'sftp_login'
| 'console_command_executed'
| 'console_cleared'
| 'backup_created'
| 'backup_renamed'
| 'backup_restored'
| 'backup_deleted'
| 'startup_command_modified'
| 'java_runtime_modified'
| 'java_version_modified'
export type Action = {
action: ActionName | string
metadata?: unknown
}
export type UserPermissionsActionMetadata = {
user_id: string
permissions?: ServerUsers.v1.UserScope | null
}
export type ActionUser =
| {
type: 'user'
user_id: string
}
| {
type: 'support'
user_id?: string | null
}
export type ActionEntry = {
actor: ActionUser
action: Action
server_id: string
world_id?: string | null
timestamp: string
}
export type UserResp = {
username: string
avatar_url?: string | null
}
export type AddonResp = {
title: string
slug?: string | null
icon_url?: string | null
version?: string | null
}
export type VersionResp = {
name: string
version_number?: string | null
}
export type ActionLogResponse = {
next_offset?: number | null
data: ActionEntry[]
users: Record<string, UserResp>
addons: Record<string, AddonResp>
versions: Record<string, VersionResp>
}
export type ActionLogFilter = {
users?: string[]
worlds?: Array<string | null>
actions?: ActionName[]
}
export type ListActionLogOptions = {
filter?: ActionLogFilter
limit?: number
offset?: number
order?: SortOrder
min_datetime?: string
max_datetime?: string
}
}
}
export namespace Transfers {
export namespace Internal {
export type ProvisionOptions = {
region?: string | null
node_tags: string[]
}
export type ScheduleServerTransfersRequest = {
server_ids: string[]
scheduled_at?: string | null
target_region?: string | null
node_tags?: string[]
reason?: string | null
}
export type ScheduleNodeTransfersRequest = {
node_hostnames: string[]
scheduled_at?: string | null
target_region?: string | null
node_tags?: string[]
reason?: string | null
cordon_nodes?: boolean
tag_nodes?: string | null
}
export type ScheduleTransfersResponse = {
batch_id: number
scheduled_count: number
}
export type CancelTransfersRequest = {
batch_ids: number[]
}
export type CancelTransfersResponse = {
cancelled_count: number
}
export type TransferLogBatchEntry = {
id: number
created_by: string
created_at: string
reason?: string | null
scheduled_at: string
cancelled: boolean
log_count: number
provision_options: ProvisionOptions
}
export type TransferHistoryQuery = {
page?: number
page_size?: number
}
export type TransferHistoryResponse = {
batches: TransferLogBatchEntry[]
total: number
page: number
page_size: number
}
}
}
export namespace Content {
export namespace v1 {
export type AddonKind = 'mod' | 'plugin' | 'datapack' | 'shader' | 'resourcepack'
@@ -222,11 +503,66 @@ export namespace Archon {
}
}
export namespace ServerUsers {
export namespace v1 {
export type ServerUserRole = 'Owner' | 'Editor' | 'Viewer' | 'Unknown'
export type AssignableServerUserRole = Exclude<ServerUserRole, 'Owner' | 'Unknown'>
export const UserScope = {
NONE: '',
SERVER_ADMIN: 'SERVER_ADMIN',
BASE_READ: 'BASE_READ',
POWER_ACTIONS: 'POWER_ACTIONS',
EXEC_COMMANDS: 'EXEC_COMMANDS',
FILES_WRITE: 'FILES_WRITE',
SETUP: 'SETUP',
BACKUPS: 'BACKUPS',
ADVANCED: 'ADVANCED',
RESET_SERVER: 'RESET_SERVER',
MANAGE_USERS: 'MANAGE_USERS',
SUPPORT_AGENT: 'SUPPORT_AGENT',
INFRA_MANAGER: 'INFRA_MANAGER',
INFRA_MANAGER_READ: 'INFRA_MANAGER_READ',
INFRA_SERVERS_XFER: 'INFRA_SERVERS_XFER',
INFRA_USERS: 'INFRA_USERS',
} as const
export type UserScope = string | number
export type UserResp = {
id: string
username: string
avatar_url?: string | null
}
export type ServerUser = {
user: UserResp
added_on?: string | null
last_invite_sent?: string | null
permissions: UserScope
}
export type AddServerUserRequest = {
server_id?: string | null
user_id: string
added_on?: string | null
role: ServerUserRole
}
export type ReinviteResponse = {
sent: boolean
cooldown_seconds: number | null
}
}
}
export namespace Servers {
export namespace v0 {
export type ServerGetResponse = {
servers: Server[]
pagination: Pagination
users: Record<string, ServerOwner>
}
export type Pagination = {
@@ -236,6 +572,12 @@ export namespace Archon {
total_items: number
}
export type ServerOwner = {
id: string
username: string
avatar_url?: string | null
}
export type Status = 'installing' | 'broken' | 'available' | 'suspended'
export type SuspensionReason =
@@ -281,12 +623,15 @@ export namespace Archon {
node: NodeInfo | null
flows: Flows
is_medal: boolean
current_user_permissions: UserScope
medal_expires?: string
}
export type UserScope = number
export type Net = {
ip: string
ip: string | null
port: number
domain: string
}
@@ -422,9 +767,9 @@ export namespace Archon {
modloader: string
modloader_version: string
game_version: string
java_version: number
invocation: string
original_invocation: string
java_version: number | null
invocation: string | null
original_invocation: string | null
}
export type Region = {
@@ -555,6 +900,106 @@ export namespace Archon {
}
}
export namespace Sync {
export namespace v1 {
export type SyncCategory = 'backup' | 'users' | 'server' | 'protocol' | 'world'
export type SyncIntent = 'all' | SyncCategory | SyncCategory[]
export type BackupOperationStatus = 'completed' | 'cancelled' | 'failed' | 'timed-out'
export type ServerNetworkPort = { port: number; name: string }
export type ProtocolResetEvent = { type: 'protocol.reset' }
export type ProtocolInvalidEvent = { type: 'protocol.invalid' }
export type ProtocolErrorEvent = { type: 'protocol.error'; error: string }
export type BackupNewEvent = { type: 'backup.new'; id: string }
export type BackupPatchEvent = {
type: 'backup.patch'
world_id: string
backup_id: string
name: string
}
export type BackupDeleteEvent = {
type: 'backup.delete'
world_id: string
backup_id: string
}
export type BackupOperationStartEvent = {
type:
| 'backup.operation.create.init'
| 'backup.operation.create.start'
| 'backup.operation.restore.init'
| 'backup.operation.restore.start'
world_id: string
backup_id: string
operation_id: number
}
export type BackupOperationDoneEvent = {
type: 'backup.operation.create.done' | 'backup.operation.restore.done'
world_id: string
backup_id: string
operation_id: number
status: BackupOperationStatus
}
export type ServerPatchEvent = {
type: 'server.patch'
name: string
subdomain: string
}
export type ServerNetworkPatchEvent = {
type: 'server.network.patch'
ports: ServerNetworkPort[]
}
export type ServerTransferEvent = {
type: 'server.transfer.start' | 'server.transfer.done'
target_node: string
}
export type UsersPatchEvent = { type: 'users.patch' }
export type WorldPatchEvent = {
type: 'world.patch'
world_id: string
name: string
}
export type WorldStartupPatchEvent = {
type: 'world.startup.patch'
world_id: string
java_version: number | null
invocation: string | null
original_invocation: string | null
}
export type WorldContentAddonPatchEvent = {
type: 'world.content.addon.patch'
world_id: string
specs: Archon.Content.v1.Addon[]
}
export type WorldContentBaseUpdateEvent = {
type: 'world.content.base.update'
world_id: string
spec: Archon.Content.v1.Addons
}
export type SyncEvent =
| ProtocolResetEvent
| ProtocolInvalidEvent
| ProtocolErrorEvent
| BackupNewEvent
| BackupPatchEvent
| BackupDeleteEvent
| BackupOperationStartEvent
| BackupOperationDoneEvent
| ServerPatchEvent
| ServerNetworkPatchEvent
| ServerTransferEvent
| UsersPatchEvent
| WorldPatchEvent
| WorldStartupPatchEvent
| WorldContentAddonPatchEvent
| WorldContentBaseUpdateEvent
}
}
export namespace Websocket {
export namespace v0 {
export type WSAuth = {
+12
View File
@@ -1,12 +1,17 @@
import type { AbstractModrinthClient } from '../core/abstract-client'
import type { AbstractModule } from '../core/abstract-module'
import { ArchonActionsV1Module } from './archon/actions/v1'
import { ArchonBackupsV1Module } from './archon/backups/v1'
import { ArchonBackupsQueueV1Module } from './archon/backups-queue/v1'
import { ArchonContentV1Module } from './archon/content/v1'
import { ArchonNodesInternalModule } from './archon/nodes/internal'
import { ArchonNoticesV0Module } from './archon/notices/v0'
import { ArchonOptionsV1Module } from './archon/options/v1'
import { ArchonPropertiesV1Module } from './archon/properties/v1'
import { ArchonServerUsersV1Module } from './archon/server-users/v1'
import { ArchonServersV0Module } from './archon/servers/v0'
import { ArchonServersV1Module } from './archon/servers/v1'
import { ArchonTransfersInternalModule } from './archon/transfers/internal'
import { ISO3166Module } from './iso3166'
import { KyrosContentV1Module } from './kyros/content/v1'
import { KyrosFilesV0Module } from './kyros/files/v0'
@@ -21,6 +26,7 @@ import { LabrinthBillingInternalModule } from './labrinth/billing/internal'
import { LabrinthCampaignInternalModule } from './labrinth/campaign/internal'
import { LabrinthCollectionsModule } from './labrinth/collections'
import { LabrinthExternalProjectsInternalModule } from './labrinth/external-projects/internal'
import { LabrinthFriendsV3Module } from './labrinth/friends/v3'
import { LabrinthGlobalsInternalModule } from './labrinth/globals/internal'
import { LabrinthLimitsV3Module } from './labrinth/limits/v3'
import { LabrinthModerationInternalModule } from './labrinth/moderation/internal'
@@ -61,13 +67,18 @@ type ModuleConstructor = new (client: AbstractModrinthClient) => AbstractModule
* TODO: Better way? Probably not
*/
export const MODULE_REGISTRY = {
archon_actions_v1: ArchonActionsV1Module,
archon_backups_queue_v1: ArchonBackupsQueueV1Module,
archon_backups_v1: ArchonBackupsV1Module,
archon_content_v1: ArchonContentV1Module,
archon_nodes_internal: ArchonNodesInternalModule,
archon_notices_v0: ArchonNoticesV0Module,
archon_options_v1: ArchonOptionsV1Module,
archon_properties_v1: ArchonPropertiesV1Module,
archon_server_users_v1: ArchonServerUsersV1Module,
archon_servers_v0: ArchonServersV0Module,
archon_servers_v1: ArchonServersV1Module,
archon_transfers_internal: ArchonTransfersInternalModule,
iso3166_data: ISO3166Module,
mclogs_insights_v1: MclogsInsightsV1Module,
mclogs_logs_v1: MclogsLogsV1Module,
@@ -84,6 +95,7 @@ export const MODULE_REGISTRY = {
labrinth_campaign_internal: LabrinthCampaignInternalModule,
labrinth_collections: LabrinthCollectionsModule,
labrinth_external_projects_internal: LabrinthExternalProjectsInternalModule,
labrinth_friends_v3: LabrinthFriendsV3Module,
labrinth_globals_internal: LabrinthGlobalsInternalModule,
labrinth_moderation_internal: LabrinthModerationInternalModule,
labrinth_notifications_v2: LabrinthNotificationsV2Module,
@@ -0,0 +1,47 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Labrinth } from '../types'
export class LabrinthFriendsV3Module extends AbstractModule {
public getModuleID(): string {
return 'labrinth_friends_v3'
}
/**
* Get friends and pending friend requests for the authenticated user
*
* @returns Promise resolving to friend relationships
*/
public async list(): Promise<Labrinth.Friends.v3.UserFriend[]> {
return this.client.request<Labrinth.Friends.v3.UserFriend[]>('/friends', {
api: 'labrinth',
version: 3,
method: 'GET',
})
}
/**
* Send or accept a friend request
*
* @param idOrUsername - The target user's ID or username
*/
public async add(idOrUsername: string): Promise<void> {
return this.client.request(`/friend/${encodeURIComponent(idOrUsername)}`, {
api: 'labrinth',
version: 3,
method: 'POST',
})
}
/**
* Remove a friend or pending friend request
*
* @param idOrUsername - The target user's ID or username
*/
public async remove(idOrUsername: string): Promise<void> {
return this.client.request(`/friend/${encodeURIComponent(idOrUsername)}`, {
api: 'labrinth',
version: 3,
method: 'DELETE',
})
}
}
@@ -4,6 +4,7 @@ export * from './auth/v2'
export * from './billing/internal'
export * from './collections'
export * from './external-projects/internal'
export * from './friends/v3'
export * from './globals/internal'
export * from './limits/v3'
export * from './moderation/internal'
@@ -1350,6 +1350,12 @@ export namespace Labrinth {
github_id?: number
}
export type SearchUser = {
id: string
username: string
avatar_url: string | null
}
export type AllProjectsResponse = {
projects: Projects.v3.Project[]
organizations: Record<string, Organizations.v3.Organization>
@@ -1357,6 +1363,17 @@ export namespace Labrinth {
}
}
export namespace Friends {
export namespace v3 {
export type UserFriend = {
id: string
friend_id: string
accepted: boolean
created: string
}
}
}
export namespace ServerPing {
export namespace Internal {
export type MinecraftJavaPingRequest = {
@@ -37,6 +37,25 @@ export class LabrinthUsersV3Module extends AbstractModule {
)
}
/**
* Search users by username prefix.
*
* @param query - Username search query
* @returns Promise resolving to compact user search results
*
* GET /v3/users/search?query=:query
*/
public async search(query: string): Promise<Labrinth.Users.v3.SearchUser[]> {
return this.client.request<Labrinth.Users.v3.SearchUser[]>(
`/users/search?query=${encodeURIComponent(query)}`,
{
api: 'labrinth',
version: 3,
method: 'GET',
},
)
}
/**
* Get all projects the authenticated user can access directly or through
* their organizations.