feat(mantis): add schema and models#1417
Conversation
Walking-skeleton T01 of Mantis Bug Tracker integration. Idempotent CREATE TABLE / CREATE INDEX for mantis_configs, mantis_issue_watches, and mantis_issue_watch_tasks plus Go models (MantisConfig, MantisIssueWatch, MantisFilter, MantisIssue). E2E reset wipes the new per-workspace tables before task deletion so the future Mantis forward poller can't recreate rows mid-reset. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📝 WalkthroughSummary by CodeRabbit
WalkthroughIntroduces the Mantis Bug Tracker integration foundation: a new ChangesMantis Integration Foundation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
| Filename | Overview |
|---|---|
| apps/backend/internal/mantis/models.go | New Go models for Mantis integration — well-documented and mirrors Jira/Linear shapes, but MantisIssueWatch.Filter MantisFilter lacks a db tag and a sql.Scanner implementation, making it unscannable by sqlx despite the struct otherwise carrying full db tag coverage. |
| apps/backend/internal/task/repository/sqlite/base_schema.go | Adds idempotent mantisSchemaDDL (3 tables + 1 index) and wires initMantisSchema into initSchema; DDL pattern is consistent with existing schema blocks. max_inflight_tasks INTEGER DEFAULT 5 matches the Linear precedent. |
| apps/backend/internal/task/repository/sqlite/base_migrations.go | Calls initMantisSchema at the end of runMigrations so existing installs get the tables without a dedicated ADD COLUMN migration — correct belt-and-suspenders approach. |
| apps/backend/cmd/kandev/e2e_reset.go | Adds deleteMantisStateForReset (FK-ordered DELETE sequence) and calls it before task deletion, respecting the E2E reset invariant documented in CLAUDE.md. |
| apps/backend/cmd/kandev/e2e_reset_test.go | New unit tests cover workspace isolation and idempotency for deleteMantisStateForReset; fixtures correctly seed all three FK-linked tables. |
| apps/backend/internal/task/repository/sqlite/mantis_schema_test.go | Schema tests cover table/index existence, idempotency, and an INSERT round-trip — but the round-trip never SELECTs back into the model structs, so the Filter scan incompatibility in MantisIssueWatch is undetectable by this suite. |
Entity Relationship Diagram
%%{init: {'theme': 'neutral'}}%%
erDiagram
mantis_configs {
TEXT workspace_id PK
TEXT base_url
TEXT username
TEXT auth_method
INTEGER default_project_id
DATETIME last_checked_at
INTEGER last_ok
TEXT last_error
DATETIME created_at
DATETIME updated_at
}
mantis_issue_watches {
TEXT id PK
TEXT workspace_id
TEXT workflow_id
TEXT workflow_step_id
TEXT filter
TEXT agent_profile_id
TEXT executor_profile_id
TEXT prompt
INTEGER enabled
INTEGER poll_interval_seconds
INTEGER max_inflight_tasks
DATETIME last_polled_at
TEXT last_error
DATETIME last_error_at
DATETIME created_at
DATETIME updated_at
}
mantis_issue_watch_tasks {
TEXT id PK
TEXT issue_watch_id FK
TEXT issue_id
TEXT issue_url
TEXT task_id
DATETIME created_at
}
mantis_issue_watches ||--o{ mantis_issue_watch_tasks : "ON DELETE CASCADE"
mantis_configs ||--o{ mantis_issue_watches : "workspace_id (logical)"
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
erDiagram
mantis_configs {
TEXT workspace_id PK
TEXT base_url
TEXT username
TEXT auth_method
INTEGER default_project_id
DATETIME last_checked_at
INTEGER last_ok
TEXT last_error
DATETIME created_at
DATETIME updated_at
}
mantis_issue_watches {
TEXT id PK
TEXT workspace_id
TEXT workflow_id
TEXT workflow_step_id
TEXT filter
TEXT agent_profile_id
TEXT executor_profile_id
TEXT prompt
INTEGER enabled
INTEGER poll_interval_seconds
INTEGER max_inflight_tasks
DATETIME last_polled_at
TEXT last_error
DATETIME last_error_at
DATETIME created_at
DATETIME updated_at
}
mantis_issue_watch_tasks {
TEXT id PK
TEXT issue_watch_id FK
TEXT issue_id
TEXT issue_url
TEXT task_id
DATETIME created_at
}
mantis_issue_watches ||--o{ mantis_issue_watch_tasks : "ON DELETE CASCADE"
mantis_configs ||--o{ mantis_issue_watches : "workspace_id (logical)"
Comments Outside Diff (1)
-
apps/backend/internal/mantis/models.go, line 355 (link)Filter MantisFiltercannot be scanned by sqlxThe
Filterfield is missing adbtag ANDMantisFilterdoesn't implementsql.Scanner/driver.Valuer. Every other field on this struct carries an explicitdbtag that aligns with the DDL column name, strongly implying this struct is intended to be used as a direct sqlx scan target. However, sqlx cannot deserialize aTEXTJSON column into an arbitrary struct — it will fail at runtime with a type-mismatch error the first time T03 repository code doessqlx.Get(&watch, "SELECT * FROM mantis_issue_watches …").The Linear analog (
internal/linear/store_issue_watch.go) avoids this with a separate internalissueWatchRowstruct carryingFilterJSON string \db:"filter_json"`and aToModel()that unmarshals it. Mantis should follow the same pattern — or at minimum rename the DDL column tofilter_json, change the field toFilterJSON string `db:"filter_json"`, and marshal/unmarshal in the repository layer. The current design guarantees a scan failure that the INSERT-only test inmantis_schema_test.go` will not catch.
Reviews (1): Last reviewed commit: "feat(mantis): add schema and models" | Re-trigger Greptile
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/backend/cmd/kandev/e2e_reset_test.go`:
- Around line 79-85: The t.Cleanup registration for closing sqlxDB is placed
after the NewWithDB call that could fail with t.Fatalf, which means cleanup will
never be registered if NewWithDB returns an error, leaving the database
connection open. Move the t.Cleanup call immediately after the sqlx.NewDb call
(before NewWithDB is invoked) so that the cleanup is registered before any
potential fatal error path, ensuring the database connection is always closed
even if NewWithDB fails.
In `@apps/backend/internal/task/repository/sqlite/mantis_schema_test.go`:
- Around line 26-31: The t.Cleanup function is registered after calling
NewWithDB(), but since NewWithDB() can trigger t.Fatalf() before cleanup is
registered, this creates a resource leak for the sqlxDB handle. Move the
t.Cleanup registration line that closes sqlxDB to immediately after creating
sqlxDB with sqlx.NewDb() and before calling NewWithDB() to ensure cleanup is
registered before any code path that could cause a fatal error.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 91bbe933-edf7-4fd7-8a42-aa87547c4600
📒 Files selected for processing (6)
apps/backend/cmd/kandev/e2e_reset.goapps/backend/cmd/kandev/e2e_reset_test.goapps/backend/internal/mantis/models.goapps/backend/internal/task/repository/sqlite/base_migrations.goapps/backend/internal/task/repository/sqlite/base_schema.goapps/backend/internal/task/repository/sqlite/mantis_schema_test.go
| sqlxDB := sqlx.NewDb(dbConn, "sqlite3") | ||
| repo, err := sqliterepo.NewWithDB(sqlxDB, sqlxDB, nil) | ||
| if err != nil { | ||
| t.Fatalf("new repo: %v", err) | ||
| } | ||
| t.Cleanup(func() { _ = sqlxDB.Close() }) | ||
| return repo, sqlxDB |
There was a problem hiding this comment.
Move cleanup registration above NewWithDB fatal path.
If NewWithDB(...) fails, t.Fatalf exits before cleanup is registered, leaving the DB open on that path.
Suggested fix
sqlxDB := sqlx.NewDb(dbConn, "sqlite3")
+ t.Cleanup(func() { _ = sqlxDB.Close() })
repo, err := sqliterepo.NewWithDB(sqlxDB, sqlxDB, nil)
if err != nil {
t.Fatalf("new repo: %v", err)
}
- t.Cleanup(func() { _ = sqlxDB.Close() })
return repo, sqlxDBAs per coding guidelines, “Register t.Cleanup immediately after creating resources that need teardown ... before any t.Fatal/t.Fatalf path to prevent resource leaks.”
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| sqlxDB := sqlx.NewDb(dbConn, "sqlite3") | |
| repo, err := sqliterepo.NewWithDB(sqlxDB, sqlxDB, nil) | |
| if err != nil { | |
| t.Fatalf("new repo: %v", err) | |
| } | |
| t.Cleanup(func() { _ = sqlxDB.Close() }) | |
| return repo, sqlxDB | |
| sqlxDB := sqlx.NewDb(dbConn, "sqlite3") | |
| t.Cleanup(func() { _ = sqlxDB.Close() }) | |
| repo, err := sqliterepo.NewWithDB(sqlxDB, sqlxDB, nil) | |
| if err != nil { | |
| t.Fatalf("new repo: %v", err) | |
| } | |
| return repo, sqlxDB |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/backend/cmd/kandev/e2e_reset_test.go` around lines 79 - 85, The
t.Cleanup registration for closing sqlxDB is placed after the NewWithDB call
that could fail with t.Fatalf, which means cleanup will never be registered if
NewWithDB returns an error, leaving the database connection open. Move the
t.Cleanup call immediately after the sqlx.NewDb call (before NewWithDB is
invoked) so that the cleanup is registered before any potential fatal error
path, ensuring the database connection is always closed even if NewWithDB fails.
Source: Coding guidelines
| sqlxDB := sqlx.NewDb(dbConn, "sqlite3") | ||
| repo, err := NewWithDB(sqlxDB, sqlxDB, nil) | ||
| if err != nil { | ||
| t.Fatalf("new repo: %v", err) | ||
| } | ||
| t.Cleanup(func() { _ = sqlxDB.Close() }) |
There was a problem hiding this comment.
Register t.Cleanup immediately after creating sqlxDB.
NewWithDB(...) can hit t.Fatalf before cleanup is registered, leaking the DB handle on that path.
Suggested fix
sqlxDB := sqlx.NewDb(dbConn, "sqlite3")
+ t.Cleanup(func() { _ = sqlxDB.Close() })
repo, err := NewWithDB(sqlxDB, sqlxDB, nil)
if err != nil {
t.Fatalf("new repo: %v", err)
}
- t.Cleanup(func() { _ = sqlxDB.Close() })
return repo, sqlxDBAs per coding guidelines, “Register t.Cleanup immediately after creating resources that need teardown ... before any t.Fatal/t.Fatalf path to prevent resource leaks.”
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| sqlxDB := sqlx.NewDb(dbConn, "sqlite3") | |
| repo, err := NewWithDB(sqlxDB, sqlxDB, nil) | |
| if err != nil { | |
| t.Fatalf("new repo: %v", err) | |
| } | |
| t.Cleanup(func() { _ = sqlxDB.Close() }) | |
| sqlxDB := sqlx.NewDb(dbConn, "sqlite3") | |
| t.Cleanup(func() { _ = sqlxDB.Close() }) | |
| repo, err := NewWithDB(sqlxDB, sqlxDB, nil) | |
| if err != nil { | |
| t.Fatalf("new repo: %v", err) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/backend/internal/task/repository/sqlite/mantis_schema_test.go` around
lines 26 - 31, The t.Cleanup function is registered after calling NewWithDB(),
but since NewWithDB() can trigger t.Fatalf() before cleanup is registered, this
creates a resource leak for the sqlxDB handle. Move the t.Cleanup registration
line that closes sqlxDB to immediately after creating sqlxDB with sqlx.NewDb()
and before calling NewWithDB() to ensure cleanup is registered before any code
path that could cause a fatal error.
Source: Coding guidelines
There was a problem hiding this comment.
2 issues found across 6 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/backend/internal/mantis/models.go">
<violation number="1" location="apps/backend/internal/mantis/models.go:56">
P2: Doc comment references `filterIsEmpty` which does not exist in the Mantis package</violation>
</file>
<file name="apps/backend/cmd/kandev/e2e_reset_test.go">
<violation number="1" location="apps/backend/cmd/kandev/e2e_reset_test.go:79">
P3: Register `t.Cleanup` immediately after creating `sqlxDB`; otherwise a `NewWithDB` failure path exits via `t.Fatalf` before cleanup is registered and can leak the DB handle.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| // translates it to query parameters at request time. | ||
| // | ||
| // All fields are optional; an empty filter matches every issue the API token | ||
| // can see. Watch creation rejects fully-empty filters via filterIsEmpty. |
There was a problem hiding this comment.
P2: Doc comment references filterIsEmpty which does not exist in the Mantis package
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/backend/internal/mantis/models.go, line 56:
<comment>Doc comment references `filterIsEmpty` which does not exist in the Mantis package</comment>
<file context>
@@ -0,0 +1,241 @@
+// translates it to query parameters at request time.
+//
+// All fields are optional; an empty filter matches every issue the API token
+// can see. Watch creation rejects fully-empty filters via filterIsEmpty.
+type MantisFilter struct {
+ // ProjectID restricts to a single Mantis project. 0 means "all projects
</file context>
| sqlxDB := sqlx.NewDb(dbConn, "sqlite3") | ||
| repo, err := sqliterepo.NewWithDB(sqlxDB, sqlxDB, nil) | ||
| if err != nil { | ||
| t.Fatalf("new repo: %v", err) | ||
| } | ||
| t.Cleanup(func() { _ = sqlxDB.Close() }) | ||
| return repo, sqlxDB |
There was a problem hiding this comment.
P3: Register t.Cleanup immediately after creating sqlxDB; otherwise a NewWithDB failure path exits via t.Fatalf before cleanup is registered and can leak the DB handle.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/backend/cmd/kandev/e2e_reset_test.go, line 79:
<comment>Register `t.Cleanup` immediately after creating `sqlxDB`; otherwise a `NewWithDB` failure path exits via `t.Fatalf` before cleanup is registered and can leak the DB handle.</comment>
<file context>
@@ -0,0 +1,152 @@
+ if err != nil {
+ t.Fatalf("open sqlite: %v", err)
+ }
+ sqlxDB := sqlx.NewDb(dbConn, "sqlite3")
+ repo, err := sqliterepo.NewWithDB(sqlxDB, sqlxDB, nil)
+ if err != nil {
</file context>
| sqlxDB := sqlx.NewDb(dbConn, "sqlite3") | |
| repo, err := sqliterepo.NewWithDB(sqlxDB, sqlxDB, nil) | |
| if err != nil { | |
| t.Fatalf("new repo: %v", err) | |
| } | |
| t.Cleanup(func() { _ = sqlxDB.Close() }) | |
| return repo, sqlxDB | |
| sqlxDB := sqlx.NewDb(dbConn, "sqlite3") | |
| t.Cleanup(func() { _ = sqlxDB.Close() }) | |
| repo, err := sqliterepo.NewWithDB(sqlxDB, sqlxDB, nil) | |
| if err != nil { | |
| t.Fatalf("new repo: %v", err) | |
| } | |
| return repo, sqlxDB |
|
Never heard or used Mantis before, what kind of industries use it? Can you share the end goal? Mantis integration e2e (backend + UI changes) to create tasks from Mantis tickets? Seems like you're creating small PRs, we appreciate it but for this kind of integration a single PR is better. Allows us to see the final flow e2e and confirm the ui changes at the same time. |
|
Hi Carlos Martis is a well known Bug tracker...Could be easily installed and hosted on yor own.. using a database and php for the Implementation. Cheers Stefan Am 18.06.2026 00:05 schrieb Carlos Florêncio ***@***.***>:carlosflorencio left a comment (kdlbs/kandev#1417)
Never heard or used Mantis before, what kind of industries use it?
Can you share the end goal? Mantis integration e2e (backend + UI changes) to create tasks from Mantis tickets?
Seems like you're creating small PRs, we appreciate it but for this kind of integration a single PR is better. Allows us to see the final flow e2e and confirm the ui changes at the same time.
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you authored the thread.Message ID: ***@***.***>
|
|
Hey @StefanFVogel you didn't answer my other points 💪 |
|
The idea is to have the bugtracker kandev und Sourcecode Controllsystem in sync...A bug/task apears in the tracker and kandev synchronous... the working Moses the bug in the tracker... at the end the merge close the bug and archive the kandev task...If you have 3 parallel projects with different trackern it is hard to keep everything in syncAm 18.06.2026 21:15 schrieb Carlos Florêncio ***@***.***>:carlosflorencio left a comment (kdlbs/kandev#1417)
Hey @StefanFVogel you didn't answer my other points 💪
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: ***@***.***>
Am 18.06.2026 21:15 schrieb Carlos Florêncio ***@***.***>:carlosflorencio left a comment (kdlbs/kandev#1417)
Hey @StefanFVogel you didn't answer my other points 💪
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: ***@***.***>
Am 18.06.2026 21:15 schrieb Carlos Florêncio ***@***.***>:carlosflorencio left a comment (kdlbs/kandev#1417)
Hey @StefanFVogel you didn't answer my other points 💪
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: ***@***.***>
Am 18.06.2026 21:15 schrieb Carlos Florêncio ***@***.***>:carlosflorencio left a comment (kdlbs/kandev#1417)
Hey @StefanFVogel you didn't answer my other points 💪
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: ***@***.***>
Am 18.06.2026 21:15 schrieb Carlos Florêncio ***@***.***>:carlosflorencio left a comment (kdlbs/kandev#1417)
Hey @StefanFVogel you didn't answer my other points 💪
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: ***@***.***>
Am 18.06.2026 21:15 schrieb Carlos Florêncio ***@***.***>:carlosflorencio left a comment (kdlbs/kandev#1417)
Hey @StefanFVogel you didn't answer my other points 💪
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: ***@***.***>
Am 18.06.2026 21:15 schrieb Carlos Florêncio ***@***.***>:carlosflorencio left a comment (kdlbs/kandev#1417)
Hey @StefanFVogel you didn't answer my other points 💪
—Reply to this email directly, view it on GitHub, or unsubscribe.You are receiving this because you were mentioned.Message ID: ***@***.***>
|
|
Keep the whole implementation in a single PR 💪 (not multiple small ones), so that i can review when ready. I will close your other #1418, bring all your changes into this one and update the title + description. Regards |
|
Will switch into draft while you bring all changes to this PR. |
Summary
Walking-skeleton T01 of the Mantis Bug Tracker integration:
mantis_configs,mantis_issue_watches, andmantis_issue_watch_taskswired intoinitSchemaand re-applied fromrunMigrationsso the schema lands on both fresh and existing installs.internal/mantis/models.go:MantisConfig,MantisIssueWatch,MantisFilter,MantisIssue, plus the standard request/event/result shapes that mirror the Jira and Linear packages.mantis_configs,mantis_issue_watches, and the dedup table by workspace before the task-deletion phase — same poller-race invariant that protects GitHub review watches (seeapps/backend/CLAUDE.md"E2E reset invariant"). The cleanup lives in adeleteMantisStateForResethelper so it can be unit-tested.No service, REST client, poller, or UI yet — those land in T02–T05.
Test plan
go test ./internal/task/repository/sqlite/ -run TestInitMantisSchema— fresh DB creates tables, secondinitMantisSchema+runMigrationsare no-ops, INSERT round-trip exercises every declared column.go test ./cmd/kandev/ -run TestDeleteMantisStateForReset— cleanup deletes only the target workspace and stays idempotent on empty state.go build ./... && go vet ./...clean.golangci-lint run --new-from-rev=<base> --timeout=5m ./...reports 0 issues for the changed files.🤖 Generated with Claude Code