Technical review queue (#4775)

* chore: fix typo in status message

* feat(labrinth): overhaul malware scanner report storage and routes

* chore: address some review comments

* feat: add Delphi to Docker Compose `with-delphi` profile

* chore: fix unused import Clippy lint

* feat(labrinth/delphi): use PAT token authorization with project read scopes

* chore: expose file IDs in version queries

* fix: accept null decompiled source payloads from Delphi

* tweak(labrinth): expose base62 file IDs more consistently for Delphi

* feat(labrinth/delphi): support new Delphi report severity field

* chore(labrinth): run `cargo sqlx prepare` to fix Docker build errors

* tweak: add route for fetching Delphi issue type schema, abstract Labrinth away from issue types

* chore: run `cargo sqlx prepare`

* chore: fix typo on frontend generated state file message

* feat: update to use new Delphi issue schema

* wip: tech review endpoints

* wip: add ToSchema for dependent types

* wip: report issues return

* wip

* wip: returning more data

* wip

* Fix up db query

* Delphi configuration to talk to Labrinth

* Get Delphi working with Labrinth

* Add Delphi dummy fixture

* Better Delphi logging

* Improve utoipa for tech review routes

* Add more sorting options for tech review queue

* Oops join

* New routes for fetching issues and reports

* Fix which kind of ID is returned in tech review endpoints

* Deduplicate tech review report rows

* Reduce info sent for projects

* Fetch more thread info

* Address PR comments

* fix ci

* fix postgres version mismatch

* fix version creation

* Implement routes

* fix up tech review

* Allow adding a moderation comment to Delphi rejections

* fix up rebase

* exclude rejected projects from tech review

* add status change msg to tech review thread

* cargo sqlx prepare

* also ignore withheld projects

* More filtering on issue search

* wip: report routes

* Fix up for build

* cargo sqlx prepare

* fix thread message privacy

* New tech review search route

* submit route

* details have statuses now

* add default to drid status

* dedup issue details

* fix sqlx query on empty files

* fixes

* Dedupe issue detail statuses and message on entering tech rev

* Fix qa issues

* Fix qa issues

* fix review comments

* typos

* fix ci

* feat: tech review frontend (#4781)

* chore: fix typo in status message

* feat(labrinth): overhaul malware scanner report storage and routes

* chore: address some review comments

* feat: add Delphi to Docker Compose `with-delphi` profile

* chore: fix unused import Clippy lint

* feat(labrinth/delphi): use PAT token authorization with project read scopes

* chore: expose file IDs in version queries

* fix: accept null decompiled source payloads from Delphi

* tweak(labrinth): expose base62 file IDs more consistently for Delphi

* feat(labrinth/delphi): support new Delphi report severity field

* chore(labrinth): run `cargo sqlx prepare` to fix Docker build errors

* tweak: add route for fetching Delphi issue type schema, abstract Labrinth away from issue types

* chore: run `cargo sqlx prepare`

* chore: fix typo on frontend generated state file message

* feat: update to use new Delphi issue schema

* wip: tech review endpoints

* wip: add ToSchema for dependent types

* wip: report issues return

* wip

* wip: returning more data

* wip

* Fix up db query

* Delphi configuration to talk to Labrinth

* Get Delphi working with Labrinth

* Add Delphi dummy fixture

* Better Delphi logging

* Improve utoipa for tech review routes

* Add more sorting options for tech review queue

* Oops join

* New routes for fetching issues and reports

* Fix which kind of ID is returned in tech review endpoints

* Deduplicate tech review report rows

* Reduce info sent for projects

* Fetch more thread info

* Address PR comments

* fix ci

* fix ci

* fix postgres version mismatch

* fix version creation

* Implement routes

* feat: batch scan alert

* feat: layout

* feat: introduce surface variables

* fix: theme selector

* feat: rough draft of tech review card

* feat: tab switcher

* feat: batch scan btn

* feat: api-client module for tech review

* draft: impl

* feat: auto icons

* fix: layout issues

* feat: fixes to code blocks + flag labels

* feat: temp remove mock data

* fix: search sort types

* fix: intl & lint

* chore: re-enable mock data

* fix: flag badges + auto open first issue in file tab

* feat: update for new routes

* fix: more qa issues

* feat: lazy load sources

* fix: re-enable auth middleware

* feat: impl threads

* fix: lint & severity

* feat: download btn + switch to using NavTabs with new local mode option

* feat: re-add toplevel btns

* feat: reports page consistency

* fix: consistency on project queue

* fix: icons + sizing

* fix: colors and gaps

* fix: impl endpoints

* feat: load all flags on file tab

* feat: thread generics changes

* feat: more qa

* feat: fix collapse

* fix: qa

* feat: msg modal

* fix: ISO import

* feat: qa fixes

* fix: empty state basic

* fix: collapsible region

* fix: collapse thread by default

* feat: rough draft of new process/flow

* fix labrinth build

* fix thread message privacy

* New tech review search route

* feat: qa fixes

* feat: QA changes

* fix: verdict on detail not whole issue

* fix: lint + intl

* fix: lint

* fix: thread message for tech rev verdict

* feat: use anim frames

* fix: exports + typecheck

* polish: qa changes

* feat: qa

* feat: qa polish

* feat: fix malic modal

* fix: lint

* fix: qa + lint

* fix: pagination

* fix: lint

* fix: qa

* intl extract

* fix ci

---------

Signed-off-by: Calum H. <contact@cal.engineer>
Co-authored-by: Alejandro González <me@alegon.dev>
Co-authored-by: aecsocket <aecsocket@tutanota.com>

---------

Signed-off-by: Calum H. <contact@cal.engineer>
Co-authored-by: Alejandro González <me@alegon.dev>
Co-authored-by: Calum H. <contact@cal.engineer>
This commit is contained in:
aecsocket
2025-12-20 11:43:04 +00:00
committed by GitHub
parent 1e9e13aebb
commit 39f2b0ecb6
109 changed files with 6281 additions and 2017 deletions

View File

@@ -12,6 +12,7 @@ import { LabrinthCollectionsModule } from './labrinth/collections'
import { LabrinthProjectsV2Module } from './labrinth/projects/v2'
import { LabrinthProjectsV3Module } from './labrinth/projects/v3'
import { LabrinthStateModule } from './labrinth/state'
import { LabrinthTechReviewInternalModule } from './labrinth/tech-review/internal'
type ModuleConstructor = new (client: AbstractModrinthClient) => AbstractModule
@@ -36,6 +37,7 @@ export const MODULE_REGISTRY = {
labrinth_projects_v2: LabrinthProjectsV2Module,
labrinth_projects_v3: LabrinthProjectsV3Module,
labrinth_state: LabrinthStateModule,
labrinth_tech_review_internal: LabrinthTechReviewInternalModule,
labrinth_versions_v3: LabrinthVersionsV3Module,
} as const satisfies Record<string, ModuleConstructor>

View File

@@ -3,4 +3,5 @@ export * from './collections'
export * from './projects/v2'
export * from './projects/v3'
export * from './state'
export * from './tech-review/internal'
export * from './versions/v3'

View File

@@ -0,0 +1,124 @@
import { AbstractModule } from '../../../core/abstract-module'
import type { Labrinth } from '../types'
export class LabrinthTechReviewInternalModule extends AbstractModule {
public getModuleID(): string {
return 'labrinth_tech_review_internal'
}
/**
* Search for projects awaiting technical review.
*
* Returns a flat list of file reports with associated project data, ownership
* information, and moderation threads provided as lookup maps.
*
* @param params - Search parameters including pagination, filters, and sorting
* @returns Response object containing reports array and lookup maps for projects, threads, and ownership
*
* @example
* ```typescript
* const response = await client.labrinth.tech_review_internal.searchProjects({
* limit: 20,
* page: 0,
* sort_by: 'created_asc',
* filter: {
* project_type: ['mod', 'modpack']
* }
* })
* // Access reports: response.reports
* // Access project by ID: response.projects[projectId]
* ```
*/
public async searchProjects(
params: Labrinth.TechReview.Internal.SearchProjectsRequest,
): Promise<Labrinth.TechReview.Internal.SearchResponse> {
return this.client.request<Labrinth.TechReview.Internal.SearchResponse>(
'/moderation/tech-review/search',
{
api: 'labrinth',
version: 'internal',
method: 'POST',
body: params,
},
)
}
/**
* Get detailed information about a specific file report.
*
* @param reportId - The Delphi report ID
* @returns Full report with all issues and details
*
* @example
* ```typescript
* const report = await client.labrinth.tech_review_internal.getReport('report-123')
* console.log(report.file_name, report.issues.length)
* ```
*/
public async getReport(reportId: string): Promise<Labrinth.TechReview.Internal.FileReport> {
return this.client.request<Labrinth.TechReview.Internal.FileReport>(
`/moderation/tech-review/report/${reportId}`,
{
api: 'labrinth',
version: 'internal',
method: 'GET',
},
)
}
/**
* Get detailed information about a specific issue.
*
* @param issueId - The issue ID
* @returns Issue with all its details
*
* @example
* ```typescript
* const issue = await client.labrinth.tech_review_internal.getIssue('issue-123')
* console.log(issue.issue_type, issue.status)
* ```
*/
public async getIssue(issueId: string): Promise<Labrinth.TechReview.Internal.FileIssue> {
return this.client.request<Labrinth.TechReview.Internal.FileIssue>(
`/moderation/tech-review/issue/${issueId}`,
{
api: 'labrinth',
version: 'internal',
method: 'GET',
},
)
}
/**
* Update the status of a technical review issue detail.
*
* Allows moderators to mark an individual issue detail as safe (false positive) or unsafe (malicious).
*
* @param detailId - The ID of the issue detail to update
* @param data - The verdict for the detail
* @returns Promise that resolves when the update is complete
*/
public async updateIssueDetail(
detailId: string,
data: Labrinth.TechReview.Internal.UpdateIssueRequest,
): Promise<void> {
return this.client.request<void>(`/moderation/tech-review/issue-detail/${detailId}`, {
api: 'labrinth',
version: 'internal',
method: 'PATCH',
body: data,
})
}
public async submitProject(
projectId: string,
data: Labrinth.TechReview.Internal.SubmitProjectRequest,
): Promise<void> {
return this.client.request<void>(`/moderation/tech-review/submit/${projectId}`, {
api: 'labrinth',
version: 'internal',
method: 'POST',
body: data,
})
}
}

View File

@@ -13,7 +13,7 @@ export namespace Labrinth {
price_id: string
interval: PriceDuration
status: SubscriptionStatus
created: string // ISO datetime string
created: string
metadata?: SubscriptionMetadata
}
@@ -40,8 +40,8 @@ export namespace Labrinth {
amount: number
currency_code: string
status: ChargeStatus
due: string // ISO datetime string
last_attempt: string | null // ISO datetime string
due: string
last_attempt: string | null
type: ChargeType
subscription_id: string | null
subscription_interval: PriceDuration | null
@@ -337,6 +337,10 @@ export namespace Labrinth {
monetization_status: v2.MonetizationStatus
side_types_migration_review_status: 'reviewed' | 'pending'
environment?: Environment[]
/**
* @deprecated Not recommended to use.
**/
[key: string]: unknown
}
@@ -719,4 +723,181 @@ export namespace Labrinth {
errors: unknown[]
}
}
export namespace TechReview {
export namespace Internal {
export type SearchProjectsRequest = {
limit?: number
page?: number
filter?: SearchProjectsFilter
sort_by?: SearchProjectsSort
}
export type SearchProjectsFilter = {
project_type?: string[]
}
export type SearchProjectsSort =
| 'created_asc'
| 'created_desc'
| 'severity_asc'
| 'severity_desc'
export type UpdateIssueRequest = {
verdict: 'safe' | 'unsafe'
}
export type SubmitProjectRequest = {
verdict: 'safe' | 'unsafe'
message?: string
}
export type SearchResponse = {
project_reports: ProjectReport[]
projects: Record<string, ProjectModerationInfo>
threads: Record<string, Thread>
ownership: Record<string, Ownership>
}
export type ProjectModerationInfo = {
id: string
thread_id: string
name: string
project_types: string[]
icon_url: string | null
} & Projects.v3.Project
export type ProjectReport = {
project_id: string
max_severity: DelphiSeverity | null
versions: VersionReport[]
}
export type VersionReport = {
version_id: string
files: FileReport[]
}
export type FileReport = {
report_id: string
file_id: string
created: string
flag_reason: FlagReason
severity: DelphiSeverity
file_name: string
file_size: number
download_url: string
issues: FileIssue[]
}
export type FileIssue = {
id: string
report_id: string
issue_type: string
details: ReportIssueDetail[]
}
export type ReportIssueDetail = {
id: string
issue_id: string
key: string
file_path: string
decompiled_source: string | null
data: Record<string, unknown>
severity: DelphiSeverity
status: DelphiReportIssueStatus
}
export type Ownership =
| {
kind: 'user'
id: string
name: string
icon_url?: string
}
| {
kind: 'organization'
id: string
name: string
icon_url?: string
}
export type DBThread = {
id: string
project_id?: string
report_id?: string
type_: ThreadType
messages: DBThreadMessage[]
members: string[]
}
export type DBThreadMessage = {
id: string
thread_id: string
author_id?: string
body: MessageBody
created: string
hide_identity: boolean
}
export type MessageBody =
| {
type: 'text'
body: string
private?: boolean
replying_to?: string
associated_images?: string[]
}
| {
type: 'status_change'
new_status: Projects.v2.ProjectStatus
old_status: Projects.v2.ProjectStatus
}
| {
type: 'thread_closure'
}
| {
type: 'thread_reopen'
}
| {
type: 'deleted'
private?: boolean
}
export type ThreadType = 'report' | 'project' | 'direct_message'
export type User = {
id: string
username: string
avatar_url: string
role: string
badges: number
created: string
bio?: string
}
export type ThreadMessage = {
id: string | null
author_id: string | null
body: MessageBody
created: string
hide_identity: boolean
}
export type Thread = {
id: string
type: ThreadType
project_id: string | null
report_id: string | null
messages: ThreadMessage[]
members: User[]
}
export type FlagReason = 'delphi'
export type DelphiSeverity = 'low' | 'medium' | 'high' | 'severe'
export type DelphiReportIssueStatus = 'pending' | 'safe' | 'unsafe'
}
}
}