merge: resolve conflict in issue-detail breadcrumb
Keep identifier removed from breadcrumbs per design decision. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
3a8aec7d08
34 changed files with 1606 additions and 1191 deletions
|
|
@ -179,6 +179,14 @@ func (h *Handler) loadIssueForUser(w http.ResponseWriter, r *http.Request, issue
|
|||
return db.Issue{}, false
|
||||
}
|
||||
|
||||
// Try identifier format first (e.g., "JIA-42").
|
||||
if issue, ok := h.resolveIssueByIdentifier(r.Context(), issueID, resolveWorkspaceID(r)); ok {
|
||||
if _, ok := h.requireWorkspaceMember(w, r, uuidToString(issue.WorkspaceID), "issue not found"); !ok {
|
||||
return db.Issue{}, false
|
||||
}
|
||||
return issue, true
|
||||
}
|
||||
|
||||
issue, err := h.Queries.GetIssue(r.Context(), parseUUID(issueID))
|
||||
if err != nil {
|
||||
writeError(w, http.StatusNotFound, "issue not found")
|
||||
|
|
@ -192,6 +200,64 @@ func (h *Handler) loadIssueForUser(w http.ResponseWriter, r *http.Request, issue
|
|||
return issue, true
|
||||
}
|
||||
|
||||
// resolveIssueByIdentifier tries to look up an issue by "PREFIX-NUMBER" format.
|
||||
func (h *Handler) resolveIssueByIdentifier(ctx context.Context, id, workspaceID string) (db.Issue, bool) {
|
||||
parts := splitIdentifier(id)
|
||||
if parts == nil {
|
||||
return db.Issue{}, false
|
||||
}
|
||||
if workspaceID == "" {
|
||||
return db.Issue{}, false
|
||||
}
|
||||
issue, err := h.Queries.GetIssueByNumber(ctx, db.GetIssueByNumberParams{
|
||||
WorkspaceID: parseUUID(workspaceID),
|
||||
Number: parts.number,
|
||||
})
|
||||
if err != nil {
|
||||
return db.Issue{}, false
|
||||
}
|
||||
return issue, true
|
||||
}
|
||||
|
||||
type identifierParts struct {
|
||||
prefix string
|
||||
number int32
|
||||
}
|
||||
|
||||
func splitIdentifier(id string) *identifierParts {
|
||||
idx := -1
|
||||
for i := len(id) - 1; i >= 0; i-- {
|
||||
if id[i] == '-' {
|
||||
idx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if idx <= 0 || idx >= len(id)-1 {
|
||||
return nil
|
||||
}
|
||||
numStr := id[idx+1:]
|
||||
num := 0
|
||||
for _, c := range numStr {
|
||||
if c < '0' || c > '9' {
|
||||
return nil
|
||||
}
|
||||
num = num*10 + int(c-'0')
|
||||
}
|
||||
if num <= 0 {
|
||||
return nil
|
||||
}
|
||||
return &identifierParts{prefix: id[:idx], number: int32(num)}
|
||||
}
|
||||
|
||||
// getIssuePrefix fetches the issue_prefix for a workspace.
|
||||
func (h *Handler) getIssuePrefix(ctx context.Context, workspaceID pgtype.UUID) string {
|
||||
ws, err := h.Queries.GetWorkspace(ctx, workspaceID)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return ws.IssuePrefix
|
||||
}
|
||||
|
||||
func (h *Handler) loadAgentForUser(w http.ResponseWriter, r *http.Request, agentID string) (db.Agent, bool) {
|
||||
if _, ok := requireUserID(w, r); !ok {
|
||||
return db.Agent{}, false
|
||||
|
|
|
|||
|
|
@ -90,10 +90,10 @@ func setupHandlerTestFixture(ctx context.Context, pool *pgxpool.Pool) (string, s
|
|||
|
||||
var workspaceID string
|
||||
if err := pool.QueryRow(ctx, `
|
||||
INSERT INTO workspace (name, slug, description)
|
||||
VALUES ($1, $2, $3)
|
||||
INSERT INTO workspace (name, slug, description, issue_prefix)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id
|
||||
`, "Handler Tests", handlerTestWorkspaceSlug, "Temporary workspace for handler tests").Scan(&workspaceID); err != nil {
|
||||
`, "Handler Tests", handlerTestWorkspaceSlug, "Temporary workspace for handler tests", "HAN").Scan(&workspaceID); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import (
|
|||
type IssueResponse struct {
|
||||
ID string `json:"id"`
|
||||
WorkspaceID string `json:"workspace_id"`
|
||||
Number int32 `json:"number"`
|
||||
Identifier string `json:"identifier"`
|
||||
Title string `json:"title"`
|
||||
Description *string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
|
|
@ -41,10 +43,13 @@ type agentTriggerSnapshot struct {
|
|||
Config map[string]any `json:"config"`
|
||||
}
|
||||
|
||||
func issueToResponse(i db.Issue) IssueResponse {
|
||||
func issueToResponse(i db.Issue, issuePrefix string) IssueResponse {
|
||||
identifier := issuePrefix + "-" + strconv.Itoa(int(i.Number))
|
||||
return IssueResponse{
|
||||
ID: uuidToString(i.ID),
|
||||
WorkspaceID: uuidToString(i.WorkspaceID),
|
||||
Number: i.Number,
|
||||
Identifier: identifier,
|
||||
Title: i.Title,
|
||||
Description: textToPtr(i.Description),
|
||||
Status: i.Status,
|
||||
|
|
@ -109,9 +114,10 @@ func (h *Handler) ListIssues(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
prefix := h.getIssuePrefix(ctx, parseUUID(workspaceID))
|
||||
resp := make([]IssueResponse, len(issues))
|
||||
for i, issue := range issues {
|
||||
resp[i] = issueToResponse(issue)
|
||||
resp[i] = issueToResponse(issue, prefix)
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
|
|
@ -126,7 +132,8 @@ func (h *Handler) GetIssue(w http.ResponseWriter, r *http.Request) {
|
|||
if !ok {
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, issueToResponse(issue))
|
||||
prefix := h.getIssuePrefix(r.Context(), issue.WorkspaceID)
|
||||
writeJSON(w, http.StatusOK, issueToResponse(issue, prefix))
|
||||
}
|
||||
|
||||
type CreateIssueRequest struct {
|
||||
|
|
@ -196,7 +203,24 @@ func (h *Handler) CreateIssue(w http.ResponseWriter, r *http.Request) {
|
|||
dueDate = pgtype.Timestamptz{Time: t, Valid: true}
|
||||
}
|
||||
|
||||
issue, err := h.Queries.CreateIssue(r.Context(), db.CreateIssueParams{
|
||||
// Use a transaction to atomically increment the workspace issue counter
|
||||
// and create the issue with the assigned number.
|
||||
tx, err := h.TxStarter.Begin(r.Context())
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "failed to create issue")
|
||||
return
|
||||
}
|
||||
defer tx.Rollback(r.Context())
|
||||
|
||||
qtx := h.Queries.WithTx(tx)
|
||||
issueNumber, err := qtx.IncrementIssueCounter(r.Context(), parseUUID(workspaceID))
|
||||
if err != nil {
|
||||
slog.Warn("increment issue counter failed", append(logger.RequestAttrs(r), "error", err, "workspace_id", workspaceID)...)
|
||||
writeError(w, http.StatusInternalServerError, "failed to create issue")
|
||||
return
|
||||
}
|
||||
|
||||
issue, err := qtx.CreateIssue(r.Context(), db.CreateIssueParams{
|
||||
WorkspaceID: parseUUID(workspaceID),
|
||||
Title: req.Title,
|
||||
Description: ptrToText(req.Description),
|
||||
|
|
@ -209,6 +233,7 @@ func (h *Handler) CreateIssue(w http.ResponseWriter, r *http.Request) {
|
|||
ParentIssueID: parentIssueID,
|
||||
Position: 0,
|
||||
DueDate: dueDate,
|
||||
Number: issueNumber,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Warn("create issue failed", append(logger.RequestAttrs(r), "error", err, "workspace_id", workspaceID)...)
|
||||
|
|
@ -216,7 +241,13 @@ func (h *Handler) CreateIssue(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
resp := issueToResponse(issue)
|
||||
if err := tx.Commit(r.Context()); err != nil {
|
||||
writeError(w, http.StatusInternalServerError, "failed to create issue")
|
||||
return
|
||||
}
|
||||
|
||||
prefix := h.getIssuePrefix(r.Context(), issue.WorkspaceID)
|
||||
resp := issueToResponse(issue, prefix)
|
||||
slog.Info("issue created", append(logger.RequestAttrs(r), "issue_id", uuidToString(issue.ID), "title", issue.Title, "status", issue.Status, "workspace_id", workspaceID)...)
|
||||
h.publish(protocol.EventIssueCreated, workspaceID, "member", creatorID, map[string]any{"issue": resp})
|
||||
|
||||
|
|
@ -326,7 +357,8 @@ func (h *Handler) UpdateIssue(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
resp := issueToResponse(issue)
|
||||
prefix := h.getIssuePrefix(r.Context(), issue.WorkspaceID)
|
||||
resp := issueToResponse(issue, prefix)
|
||||
slog.Info("issue updated", append(logger.RequestAttrs(r), "issue_id", id, "workspace_id", workspaceID)...)
|
||||
|
||||
assigneeChanged := (req.AssigneeType != nil || req.AssigneeID != nil) &&
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
|
@ -13,6 +14,22 @@ import (
|
|||
"github.com/multica-ai/multica/server/pkg/protocol"
|
||||
)
|
||||
|
||||
var nonAlpha = regexp.MustCompile(`[^a-zA-Z]`)
|
||||
|
||||
// generateIssuePrefix produces a 2-5 char uppercase prefix from a workspace name.
|
||||
// Examples: "Jiayuan's Workspace" → "JIA", "My Team" → "MYT", "AB" → "AB".
|
||||
func generateIssuePrefix(name string) string {
|
||||
letters := nonAlpha.ReplaceAllString(name, "")
|
||||
if len(letters) == 0 {
|
||||
return "WS"
|
||||
}
|
||||
letters = strings.ToUpper(letters)
|
||||
if len(letters) > 3 {
|
||||
letters = letters[:3]
|
||||
}
|
||||
return letters
|
||||
}
|
||||
|
||||
type WorkspaceResponse struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
|
@ -21,6 +38,7 @@ type WorkspaceResponse struct {
|
|||
Context *string `json:"context"`
|
||||
Settings any `json:"settings"`
|
||||
Repos any `json:"repos"`
|
||||
IssuePrefix string `json:"issue_prefix"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
|
@ -48,6 +66,7 @@ func workspaceToResponse(w db.Workspace) WorkspaceResponse {
|
|||
Context: textToPtr(w.Context),
|
||||
Settings: settings,
|
||||
Repos: repos,
|
||||
IssuePrefix: w.IssuePrefix,
|
||||
CreatedAt: timestampToString(w.CreatedAt),
|
||||
UpdatedAt: timestampToString(w.UpdatedAt),
|
||||
}
|
||||
|
|
@ -110,6 +129,7 @@ type CreateWorkspaceRequest struct {
|
|||
Slug string `json:"slug"`
|
||||
Description *string `json:"description"`
|
||||
Context *string `json:"context"`
|
||||
IssuePrefix *string `json:"issue_prefix"`
|
||||
}
|
||||
|
||||
func (h *Handler) CreateWorkspace(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -138,12 +158,18 @@ func (h *Handler) CreateWorkspace(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
defer tx.Rollback(r.Context())
|
||||
|
||||
issuePrefix := generateIssuePrefix(req.Name)
|
||||
if req.IssuePrefix != nil && strings.TrimSpace(*req.IssuePrefix) != "" {
|
||||
issuePrefix = strings.ToUpper(strings.TrimSpace(*req.IssuePrefix))
|
||||
}
|
||||
|
||||
qtx := h.Queries.WithTx(tx)
|
||||
ws, err := qtx.CreateWorkspace(r.Context(), db.CreateWorkspaceParams{
|
||||
Name: req.Name,
|
||||
Slug: req.Slug,
|
||||
Description: ptrToText(req.Description),
|
||||
Context: ptrToText(req.Context),
|
||||
IssuePrefix: issuePrefix,
|
||||
})
|
||||
if err != nil {
|
||||
if isUniqueViolation(err) {
|
||||
|
|
@ -179,6 +205,7 @@ type UpdateWorkspaceRequest struct {
|
|||
Context *string `json:"context"`
|
||||
Settings any `json:"settings"`
|
||||
Repos any `json:"repos"`
|
||||
IssuePrefix *string `json:"issue_prefix"`
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateWorkspace(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -218,6 +245,12 @@ func (h *Handler) UpdateWorkspace(w http.ResponseWriter, r *http.Request) {
|
|||
reposJSON, _ := json.Marshal(req.Repos)
|
||||
params.Repos = reposJSON
|
||||
}
|
||||
if req.IssuePrefix != nil {
|
||||
prefix := strings.ToUpper(strings.TrimSpace(*req.IssuePrefix))
|
||||
if prefix != "" {
|
||||
params.IssuePrefix = pgtype.Text{String: prefix, Valid: true}
|
||||
}
|
||||
}
|
||||
|
||||
ws, err := h.Queries.UpdateWorkspace(r.Context(), params)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import (
|
|||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"strconv"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"github.com/multica-ai/multica/server/internal/events"
|
||||
|
|
@ -367,15 +369,24 @@ func (s *TaskService) broadcastTaskEvent(ctx context.Context, eventType string,
|
|||
}
|
||||
|
||||
func (s *TaskService) broadcastIssueUpdated(issue db.Issue) {
|
||||
prefix := s.getIssuePrefix(issue.WorkspaceID)
|
||||
s.Bus.Publish(events.Event{
|
||||
Type: protocol.EventIssueUpdated,
|
||||
WorkspaceID: util.UUIDToString(issue.WorkspaceID),
|
||||
ActorType: "system",
|
||||
ActorID: "",
|
||||
Payload: map[string]any{"issue": issueToMap(issue)},
|
||||
Payload: map[string]any{"issue": issueToMap(issue, prefix)},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *TaskService) getIssuePrefix(workspaceID pgtype.UUID) string {
|
||||
ws, err := s.Queries.GetWorkspace(context.Background(), workspaceID)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return ws.IssuePrefix
|
||||
}
|
||||
|
||||
func (s *TaskService) createAgentComment(ctx context.Context, issueID, agentID pgtype.UUID, content, commentType string) {
|
||||
if content == "" {
|
||||
return
|
||||
|
|
@ -416,23 +427,25 @@ func (s *TaskService) createAgentComment(ctx context.Context, issueID, agentID p
|
|||
})
|
||||
}
|
||||
|
||||
func issueToMap(issue db.Issue) map[string]any {
|
||||
func issueToMap(issue db.Issue, issuePrefix string) map[string]any {
|
||||
return map[string]any{
|
||||
"id": util.UUIDToString(issue.ID),
|
||||
"workspace_id": util.UUIDToString(issue.WorkspaceID),
|
||||
"title": issue.Title,
|
||||
"description": util.TextToPtr(issue.Description),
|
||||
"status": issue.Status,
|
||||
"priority": issue.Priority,
|
||||
"assignee_type": util.TextToPtr(issue.AssigneeType),
|
||||
"assignee_id": util.UUIDToPtr(issue.AssigneeID),
|
||||
"creator_type": issue.CreatorType,
|
||||
"creator_id": util.UUIDToString(issue.CreatorID),
|
||||
"id": util.UUIDToString(issue.ID),
|
||||
"workspace_id": util.UUIDToString(issue.WorkspaceID),
|
||||
"number": issue.Number,
|
||||
"identifier": issuePrefix + "-" + strconv.Itoa(int(issue.Number)),
|
||||
"title": issue.Title,
|
||||
"description": util.TextToPtr(issue.Description),
|
||||
"status": issue.Status,
|
||||
"priority": issue.Priority,
|
||||
"assignee_type": util.TextToPtr(issue.AssigneeType),
|
||||
"assignee_id": util.UUIDToPtr(issue.AssigneeID),
|
||||
"creator_type": issue.CreatorType,
|
||||
"creator_id": util.UUIDToString(issue.CreatorID),
|
||||
"parent_issue_id": util.UUIDToPtr(issue.ParentIssueID),
|
||||
"position": issue.Position,
|
||||
"due_date": util.TimestampToPtr(issue.DueDate),
|
||||
"created_at": util.TimestampToString(issue.CreatedAt),
|
||||
"updated_at": util.TimestampToString(issue.UpdatedAt),
|
||||
"position": issue.Position,
|
||||
"due_date": util.TimestampToPtr(issue.DueDate),
|
||||
"created_at": util.TimestampToString(issue.CreatedAt),
|
||||
"updated_at": util.TimestampToString(issue.UpdatedAt),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
5
server/migrations/020_issue_number.down.sql
Normal file
5
server/migrations/020_issue_number.down.sql
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
DROP INDEX IF EXISTS idx_issue_workspace_number;
|
||||
ALTER TABLE issue DROP CONSTRAINT IF EXISTS uq_issue_workspace_number;
|
||||
ALTER TABLE issue DROP COLUMN IF EXISTS number;
|
||||
ALTER TABLE workspace DROP COLUMN IF EXISTS issue_prefix;
|
||||
ALTER TABLE workspace DROP COLUMN IF EXISTS issue_counter;
|
||||
36
server/migrations/020_issue_number.up.sql
Normal file
36
server/migrations/020_issue_number.up.sql
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
-- Add issue_prefix and issue_counter to workspace for human-readable issue IDs.
|
||||
ALTER TABLE workspace
|
||||
ADD COLUMN issue_prefix TEXT NOT NULL DEFAULT '',
|
||||
ADD COLUMN issue_counter INT NOT NULL DEFAULT 0;
|
||||
|
||||
-- Add per-workspace issue number.
|
||||
ALTER TABLE issue
|
||||
ADD COLUMN number INT NOT NULL DEFAULT 0;
|
||||
|
||||
-- Backfill: generate issue_prefix from workspace name (first 3 uppercase chars).
|
||||
UPDATE workspace SET issue_prefix = UPPER(
|
||||
LEFT(REGEXP_REPLACE(name, '[^a-zA-Z]', '', 'g'), 3)
|
||||
);
|
||||
|
||||
-- Fallback for workspaces with empty prefix after cleanup.
|
||||
UPDATE workspace SET issue_prefix = 'WS' WHERE issue_prefix = '';
|
||||
|
||||
-- Backfill: assign sequential numbers to existing issues per workspace.
|
||||
WITH numbered AS (
|
||||
SELECT id, workspace_id,
|
||||
ROW_NUMBER() OVER (PARTITION BY workspace_id ORDER BY created_at ASC) AS rn
|
||||
FROM issue
|
||||
)
|
||||
UPDATE issue SET number = numbered.rn
|
||||
FROM numbered WHERE issue.id = numbered.id;
|
||||
|
||||
-- Update workspace counters to match.
|
||||
UPDATE workspace SET issue_counter = COALESCE(
|
||||
(SELECT MAX(number) FROM issue WHERE issue.workspace_id = workspace.id), 0
|
||||
);
|
||||
|
||||
-- Add unique constraint.
|
||||
ALTER TABLE issue ADD CONSTRAINT uq_issue_workspace_number UNIQUE (workspace_id, number);
|
||||
|
||||
-- Index for fast lookup by workspace + number.
|
||||
CREATE INDEX idx_issue_workspace_number ON issue(workspace_id, number);
|
||||
|
|
@ -15,10 +15,10 @@ const createIssue = `-- name: CreateIssue :one
|
|||
INSERT INTO issue (
|
||||
workspace_id, title, description, status, priority,
|
||||
assignee_type, assignee_id, creator_type, creator_id,
|
||||
parent_issue_id, position, due_date
|
||||
parent_issue_id, position, due_date, number
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12
|
||||
) RETURNING id, workspace_id, title, description, status, priority, assignee_type, assignee_id, creator_type, creator_id, parent_issue_id, acceptance_criteria, context_refs, position, due_date, created_at, updated_at
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13
|
||||
) RETURNING id, workspace_id, title, description, status, priority, assignee_type, assignee_id, creator_type, creator_id, parent_issue_id, acceptance_criteria, context_refs, position, due_date, created_at, updated_at, number
|
||||
`
|
||||
|
||||
type CreateIssueParams struct {
|
||||
|
|
@ -34,6 +34,7 @@ type CreateIssueParams struct {
|
|||
ParentIssueID pgtype.UUID `json:"parent_issue_id"`
|
||||
Position float64 `json:"position"`
|
||||
DueDate pgtype.Timestamptz `json:"due_date"`
|
||||
Number int32 `json:"number"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateIssue(ctx context.Context, arg CreateIssueParams) (Issue, error) {
|
||||
|
|
@ -50,6 +51,7 @@ func (q *Queries) CreateIssue(ctx context.Context, arg CreateIssueParams) (Issue
|
|||
arg.ParentIssueID,
|
||||
arg.Position,
|
||||
arg.DueDate,
|
||||
arg.Number,
|
||||
)
|
||||
var i Issue
|
||||
err := row.Scan(
|
||||
|
|
@ -70,6 +72,7 @@ func (q *Queries) CreateIssue(ctx context.Context, arg CreateIssueParams) (Issue
|
|||
&i.DueDate,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Number,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -84,7 +87,7 @@ func (q *Queries) DeleteIssue(ctx context.Context, id pgtype.UUID) error {
|
|||
}
|
||||
|
||||
const getIssue = `-- name: GetIssue :one
|
||||
SELECT id, workspace_id, title, description, status, priority, assignee_type, assignee_id, creator_type, creator_id, parent_issue_id, acceptance_criteria, context_refs, position, due_date, created_at, updated_at FROM issue
|
||||
SELECT id, workspace_id, title, description, status, priority, assignee_type, assignee_id, creator_type, creator_id, parent_issue_id, acceptance_criteria, context_refs, position, due_date, created_at, updated_at, number FROM issue
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
|
|
@ -109,12 +112,49 @@ func (q *Queries) GetIssue(ctx context.Context, id pgtype.UUID) (Issue, error) {
|
|||
&i.DueDate,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Number,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getIssueByNumber = `-- name: GetIssueByNumber :one
|
||||
SELECT id, workspace_id, title, description, status, priority, assignee_type, assignee_id, creator_type, creator_id, parent_issue_id, acceptance_criteria, context_refs, position, due_date, created_at, updated_at, number FROM issue
|
||||
WHERE workspace_id = $1 AND number = $2
|
||||
`
|
||||
|
||||
type GetIssueByNumberParams struct {
|
||||
WorkspaceID pgtype.UUID `json:"workspace_id"`
|
||||
Number int32 `json:"number"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetIssueByNumber(ctx context.Context, arg GetIssueByNumberParams) (Issue, error) {
|
||||
row := q.db.QueryRow(ctx, getIssueByNumber, arg.WorkspaceID, arg.Number)
|
||||
var i Issue
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.WorkspaceID,
|
||||
&i.Title,
|
||||
&i.Description,
|
||||
&i.Status,
|
||||
&i.Priority,
|
||||
&i.AssigneeType,
|
||||
&i.AssigneeID,
|
||||
&i.CreatorType,
|
||||
&i.CreatorID,
|
||||
&i.ParentIssueID,
|
||||
&i.AcceptanceCriteria,
|
||||
&i.ContextRefs,
|
||||
&i.Position,
|
||||
&i.DueDate,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Number,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const listIssues = `-- name: ListIssues :many
|
||||
SELECT id, workspace_id, title, description, status, priority, assignee_type, assignee_id, creator_type, creator_id, parent_issue_id, acceptance_criteria, context_refs, position, due_date, created_at, updated_at FROM issue
|
||||
SELECT id, workspace_id, title, description, status, priority, assignee_type, assignee_id, creator_type, creator_id, parent_issue_id, acceptance_criteria, context_refs, position, due_date, created_at, updated_at, number FROM issue
|
||||
WHERE workspace_id = $1
|
||||
AND ($4::text IS NULL OR status = $4)
|
||||
AND ($5::text IS NULL OR priority = $5)
|
||||
|
|
@ -166,6 +206,7 @@ func (q *Queries) ListIssues(ctx context.Context, arg ListIssuesParams) ([]Issue
|
|||
&i.DueDate,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Number,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -189,7 +230,7 @@ UPDATE issue SET
|
|||
due_date = $9,
|
||||
updated_at = now()
|
||||
WHERE id = $1
|
||||
RETURNING id, workspace_id, title, description, status, priority, assignee_type, assignee_id, creator_type, creator_id, parent_issue_id, acceptance_criteria, context_refs, position, due_date, created_at, updated_at
|
||||
RETURNING id, workspace_id, title, description, status, priority, assignee_type, assignee_id, creator_type, creator_id, parent_issue_id, acceptance_criteria, context_refs, position, due_date, created_at, updated_at, number
|
||||
`
|
||||
|
||||
type UpdateIssueParams struct {
|
||||
|
|
@ -235,6 +276,7 @@ func (q *Queries) UpdateIssue(ctx context.Context, arg UpdateIssueParams) (Issue
|
|||
&i.DueDate,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Number,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -244,7 +286,7 @@ UPDATE issue SET
|
|||
status = $2,
|
||||
updated_at = now()
|
||||
WHERE id = $1
|
||||
RETURNING id, workspace_id, title, description, status, priority, assignee_type, assignee_id, creator_type, creator_id, parent_issue_id, acceptance_criteria, context_refs, position, due_date, created_at, updated_at
|
||||
RETURNING id, workspace_id, title, description, status, priority, assignee_type, assignee_id, creator_type, creator_id, parent_issue_id, acceptance_criteria, context_refs, position, due_date, created_at, updated_at, number
|
||||
`
|
||||
|
||||
type UpdateIssueStatusParams struct {
|
||||
|
|
@ -273,6 +315,7 @@ func (q *Queries) UpdateIssueStatus(ctx context.Context, arg UpdateIssueStatusPa
|
|||
&i.DueDate,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Number,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@ type Issue struct {
|
|||
DueDate pgtype.Timestamptz `json:"due_date"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
Number int32 `json:"number"`
|
||||
}
|
||||
|
||||
type IssueDependency struct {
|
||||
|
|
@ -256,13 +257,15 @@ type VerificationCode struct {
|
|||
}
|
||||
|
||||
type Workspace struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Settings []byte `json:"settings"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
Context pgtype.Text `json:"context"`
|
||||
Repos []byte `json:"repos"`
|
||||
ID pgtype.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Slug string `json:"slug"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Settings []byte `json:"settings"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
Context pgtype.Text `json:"context"`
|
||||
Repos []byte `json:"repos"`
|
||||
IssuePrefix string `json:"issue_prefix"`
|
||||
IssueCounter int32 `json:"issue_counter"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ import (
|
|||
)
|
||||
|
||||
const createWorkspace = `-- name: CreateWorkspace :one
|
||||
INSERT INTO workspace (name, slug, description, context)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
RETURNING id, name, slug, description, settings, created_at, updated_at, context, repos
|
||||
INSERT INTO workspace (name, slug, description, context, issue_prefix)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, name, slug, description, settings, created_at, updated_at, context, repos, issue_prefix, issue_counter
|
||||
`
|
||||
|
||||
type CreateWorkspaceParams struct {
|
||||
|
|
@ -22,6 +22,7 @@ type CreateWorkspaceParams struct {
|
|||
Slug string `json:"slug"`
|
||||
Description pgtype.Text `json:"description"`
|
||||
Context pgtype.Text `json:"context"`
|
||||
IssuePrefix string `json:"issue_prefix"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateWorkspace(ctx context.Context, arg CreateWorkspaceParams) (Workspace, error) {
|
||||
|
|
@ -30,6 +31,7 @@ func (q *Queries) CreateWorkspace(ctx context.Context, arg CreateWorkspaceParams
|
|||
arg.Slug,
|
||||
arg.Description,
|
||||
arg.Context,
|
||||
arg.IssuePrefix,
|
||||
)
|
||||
var i Workspace
|
||||
err := row.Scan(
|
||||
|
|
@ -42,6 +44,8 @@ func (q *Queries) CreateWorkspace(ctx context.Context, arg CreateWorkspaceParams
|
|||
&i.UpdatedAt,
|
||||
&i.Context,
|
||||
&i.Repos,
|
||||
&i.IssuePrefix,
|
||||
&i.IssueCounter,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -56,7 +60,7 @@ func (q *Queries) DeleteWorkspace(ctx context.Context, id pgtype.UUID) error {
|
|||
}
|
||||
|
||||
const getWorkspace = `-- name: GetWorkspace :one
|
||||
SELECT id, name, slug, description, settings, created_at, updated_at, context, repos FROM workspace
|
||||
SELECT id, name, slug, description, settings, created_at, updated_at, context, repos, issue_prefix, issue_counter FROM workspace
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
|
|
@ -73,12 +77,14 @@ func (q *Queries) GetWorkspace(ctx context.Context, id pgtype.UUID) (Workspace,
|
|||
&i.UpdatedAt,
|
||||
&i.Context,
|
||||
&i.Repos,
|
||||
&i.IssuePrefix,
|
||||
&i.IssueCounter,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWorkspaceBySlug = `-- name: GetWorkspaceBySlug :one
|
||||
SELECT id, name, slug, description, settings, created_at, updated_at, context, repos FROM workspace
|
||||
SELECT id, name, slug, description, settings, created_at, updated_at, context, repos, issue_prefix, issue_counter FROM workspace
|
||||
WHERE slug = $1
|
||||
`
|
||||
|
||||
|
|
@ -95,12 +101,27 @@ func (q *Queries) GetWorkspaceBySlug(ctx context.Context, slug string) (Workspac
|
|||
&i.UpdatedAt,
|
||||
&i.Context,
|
||||
&i.Repos,
|
||||
&i.IssuePrefix,
|
||||
&i.IssueCounter,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const incrementIssueCounter = `-- name: IncrementIssueCounter :one
|
||||
UPDATE workspace SET issue_counter = issue_counter + 1
|
||||
WHERE id = $1
|
||||
RETURNING issue_counter
|
||||
`
|
||||
|
||||
func (q *Queries) IncrementIssueCounter(ctx context.Context, id pgtype.UUID) (int32, error) {
|
||||
row := q.db.QueryRow(ctx, incrementIssueCounter, id)
|
||||
var issue_counter int32
|
||||
err := row.Scan(&issue_counter)
|
||||
return issue_counter, err
|
||||
}
|
||||
|
||||
const listWorkspaces = `-- name: ListWorkspaces :many
|
||||
SELECT w.id, w.name, w.slug, w.description, w.settings, w.created_at, w.updated_at, w.context, w.repos FROM workspace w
|
||||
SELECT w.id, w.name, w.slug, w.description, w.settings, w.created_at, w.updated_at, w.context, w.repos, w.issue_prefix, w.issue_counter FROM workspace w
|
||||
JOIN member m ON m.workspace_id = w.id
|
||||
WHERE m.user_id = $1
|
||||
ORDER BY w.created_at ASC
|
||||
|
|
@ -125,6 +146,8 @@ func (q *Queries) ListWorkspaces(ctx context.Context, userID pgtype.UUID) ([]Wor
|
|||
&i.UpdatedAt,
|
||||
&i.Context,
|
||||
&i.Repos,
|
||||
&i.IssuePrefix,
|
||||
&i.IssueCounter,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -143,9 +166,10 @@ UPDATE workspace SET
|
|||
context = COALESCE($4, context),
|
||||
settings = COALESCE($5, settings),
|
||||
repos = COALESCE($6, repos),
|
||||
issue_prefix = COALESCE($7, issue_prefix),
|
||||
updated_at = now()
|
||||
WHERE id = $1
|
||||
RETURNING id, name, slug, description, settings, created_at, updated_at, context, repos
|
||||
RETURNING id, name, slug, description, settings, created_at, updated_at, context, repos, issue_prefix, issue_counter
|
||||
`
|
||||
|
||||
type UpdateWorkspaceParams struct {
|
||||
|
|
@ -155,6 +179,7 @@ type UpdateWorkspaceParams struct {
|
|||
Context pgtype.Text `json:"context"`
|
||||
Settings []byte `json:"settings"`
|
||||
Repos []byte `json:"repos"`
|
||||
IssuePrefix pgtype.Text `json:"issue_prefix"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams) (Workspace, error) {
|
||||
|
|
@ -165,6 +190,7 @@ func (q *Queries) UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams
|
|||
arg.Context,
|
||||
arg.Settings,
|
||||
arg.Repos,
|
||||
arg.IssuePrefix,
|
||||
)
|
||||
var i Workspace
|
||||
err := row.Scan(
|
||||
|
|
@ -177,6 +203,8 @@ func (q *Queries) UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams
|
|||
&i.UpdatedAt,
|
||||
&i.Context,
|
||||
&i.Repos,
|
||||
&i.IssuePrefix,
|
||||
&i.IssueCounter,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,15 @@ WHERE id = $1;
|
|||
INSERT INTO issue (
|
||||
workspace_id, title, description, status, priority,
|
||||
assignee_type, assignee_id, creator_type, creator_id,
|
||||
parent_issue_id, position, due_date
|
||||
parent_issue_id, position, due_date, number
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13
|
||||
) RETURNING *;
|
||||
|
||||
-- name: GetIssueByNumber :one
|
||||
SELECT * FROM issue
|
||||
WHERE workspace_id = $1 AND number = $2;
|
||||
|
||||
-- name: UpdateIssue :one
|
||||
UPDATE issue SET
|
||||
title = COALESCE(sqlc.narg('title'), title),
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ SELECT * FROM workspace
|
|||
WHERE slug = $1;
|
||||
|
||||
-- name: CreateWorkspace :one
|
||||
INSERT INTO workspace (name, slug, description, context)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
INSERT INTO workspace (name, slug, description, context, issue_prefix)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING *;
|
||||
|
||||
-- name: UpdateWorkspace :one
|
||||
|
|
@ -24,9 +24,15 @@ UPDATE workspace SET
|
|||
context = COALESCE(sqlc.narg('context'), context),
|
||||
settings = COALESCE(sqlc.narg('settings'), settings),
|
||||
repos = COALESCE(sqlc.narg('repos'), repos),
|
||||
issue_prefix = COALESCE(sqlc.narg('issue_prefix'), issue_prefix),
|
||||
updated_at = now()
|
||||
WHERE id = $1
|
||||
RETURNING *;
|
||||
|
||||
-- name: IncrementIssueCounter :one
|
||||
UPDATE workspace SET issue_counter = issue_counter + 1
|
||||
WHERE id = $1
|
||||
RETURNING issue_counter;
|
||||
|
||||
-- name: DeleteWorkspace :exec
|
||||
DELETE FROM workspace WHERE id = $1;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue