From bdc0b7236b8a5dc634a775fca8d6cc32738db0db Mon Sep 17 00:00:00 2001
From: aecsocket <43144841+aecsocket@users.noreply.github.com>
Date: Thu, 17 Sep 2026 13:46:54 +0400
Subject: [PATCH 01/25] initial thread issue model
---
.../20260916120000_thread_issues.sql | 13 ++
apps/labrinth/src/database/models/ids.rs | 6 +-
apps/labrinth/src/database/models/mod.rs | 2 +-
.../src/database/models/report_item.rs | 15 +-
.../src/database/models/thread_item.rs | 91 ++++++++----
apps/labrinth/src/models/mod.rs | 1 +
apps/labrinth/src/models/v3/ids.rs | 2 +
apps/labrinth/src/models/v3/mod.rs | 1 +
apps/labrinth/src/models/v3/thread_issues.rs | 138 ++++++++++++++++++
.../src/routes/v3/project_creation.rs | 7 +-
10 files changed, 236 insertions(+), 40 deletions(-)
create mode 100644 apps/labrinth/migrations/20260916120000_thread_issues.sql
create mode 100644 apps/labrinth/src/models/v3/thread_issues.rs
diff --git a/apps/labrinth/migrations/20260916120000_thread_issues.sql b/apps/labrinth/migrations/20260916120000_thread_issues.sql
new file mode 100644
index 00000000000..26e5efa4d07
--- /dev/null
+++ b/apps/labrinth/migrations/20260916120000_thread_issues.sql
@@ -0,0 +1,13 @@
+CREATE TABLE threads_issues (
+ id BIGINT PRIMARY KEY,
+ thread_id BIGINT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ what JSONB NOT NULL,
+ why JSONB NOT NULL,
+ user_addressed BOOLEAN NOT NULL DEFAULT FALSE,
+ moderator_verified BOOLEAN NOT NULL DEFAULT FALSE,
+ verdict VARCHAR(64) NOT NULL
+);
+
+CREATE INDEX threads_issues_thread_id_verdict
+ ON threads_issues(thread_id, verdict);
diff --git a/apps/labrinth/src/database/models/ids.rs b/apps/labrinth/src/database/models/ids.rs
index bd953e7e2d2..a06ef5f34e9 100644
--- a/apps/labrinth/src/database/models/ids.rs
+++ b/apps/labrinth/src/database/models/ids.rs
@@ -6,7 +6,7 @@ use crate::models::ids::{
OAuthAccessTokenId, OAuthClientAuthorizationId, OAuthClientId,
OAuthRedirectUriId, OrganizationId, PasskeyId, PatId, PayoutId, ProductId,
ProductPriceId, ProjectId, ReportId, SessionId, TeamId, TeamMemberId,
- ThreadId, ThreadMessageId, UserSubscriptionId, VersionId,
+ ThreadId, ThreadIssueId, ThreadMessageId, UserSubscriptionId, VersionId,
};
use ariadne::ids::base62_impl::to_base62;
use ariadne::ids::{UserId, random_base62_rng, random_base62_rng_range};
@@ -249,6 +249,10 @@ db_id_interface!(
ThreadId,
generator: generate_thread_id @ "threads",
);
+db_id_interface!(
+ ThreadIssueId,
+ generator: generate_thread_issue_id @ "threads_issues",
+);
db_id_interface!(
ThreadMessageId,
generator: generate_thread_message_id @ "threads_messages",
diff --git a/apps/labrinth/src/database/models/mod.rs b/apps/labrinth/src/database/models/mod.rs
index e647428669a..fb7d4b958db 100644
--- a/apps/labrinth/src/database/models/mod.rs
+++ b/apps/labrinth/src/database/models/mod.rs
@@ -59,7 +59,7 @@ pub use project_disclosure_item::DBProjectDisclosure;
pub use project_item::DBProject;
pub use team_item::DBTeam;
pub use team_item::DBTeamMember;
-pub use thread_item::{DBThread, DBThreadMessage};
+pub use thread_item::{DBThread, DBThreadIssue, DBThreadMessage};
pub use user_item::DBUser;
pub use version_item::DBVersion;
diff --git a/apps/labrinth/src/database/models/report_item.rs b/apps/labrinth/src/database/models/report_item.rs
index 6e748d89f20..2c35a97c214 100644
--- a/apps/labrinth/src/database/models/report_item.rs
+++ b/apps/labrinth/src/database/models/report_item.rs
@@ -2,6 +2,7 @@ use crate::database::PgTransaction;
use super::ids::*;
use chrono::{DateTime, Utc};
+use eyre::WrapErr;
pub struct DBReport {
pub id: DBReportId,
@@ -126,7 +127,7 @@ impl DBReport {
pub async fn remove_full(
id: DBReportId,
transaction: &mut PgTransaction<'_>,
- ) -> Result