feat: add issue labels (DB schema, API, and service)

New labels and issue_labels tables with cascade deletes, unique
per-company name constraint. CRUD routes for labels, label filtering
on issue list, and label sync on issue create/update. All issue
responses now include labels array.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Forgotten
2026-02-25 08:38:37 -06:00
parent 9458767942
commit 6f7172c028
14 changed files with 5903 additions and 43 deletions

View File

@@ -6,6 +6,7 @@ export {
AGENT_STATUSES,
AGENT_ADAPTER_TYPES,
AGENT_ROLES,
AGENT_ICON_NAMES,
ISSUE_STATUSES,
ISSUE_PRIORITIES,
GOAL_LEVELS,
@@ -36,6 +37,7 @@ export {
type AgentStatus,
type AgentAdapterType,
type AgentRole,
type AgentIconName,
type IssueStatus,
type IssuePriority,
type GoalLevel,
@@ -73,9 +75,11 @@ export type {
AssetImage,
Project,
ProjectGoalRef,
ProjectWorkspace,
Issue,
IssueComment,
IssueAttachment,
IssueLabel,
Goal,
Approval,
ApprovalComment,
@@ -126,15 +130,21 @@ export {
type UpdateAgentPermissions,
createProjectSchema,
updateProjectSchema,
createProjectWorkspaceSchema,
updateProjectWorkspaceSchema,
type CreateProject,
type UpdateProject,
type CreateProjectWorkspace,
type UpdateProjectWorkspace,
createIssueSchema,
createIssueLabelSchema,
updateIssueSchema,
checkoutIssueSchema,
addIssueCommentSchema,
linkIssueApprovalSchema,
createIssueAttachmentMetadataSchema,
type CreateIssue,
type CreateIssueLabel,
type UpdateIssue,
type CheckoutIssue,
type AddIssueComment,

View File

@@ -10,7 +10,7 @@ export type {
AdapterEnvironmentTestResult,
} from "./agent.js";
export type { AssetImage } from "./asset.js";
export type { Project, ProjectGoalRef } from "./project.js";
export type { Project, ProjectGoalRef, ProjectWorkspace } from "./project.js";
export type {
Issue,
IssueComment,
@@ -18,6 +18,7 @@ export type {
IssueAncestorProject,
IssueAncestorGoal,
IssueAttachment,
IssueLabel,
} from "./issue.js";
export type { Goal } from "./goal.js";
export type { Approval, ApprovalComment } from "./approval.js";

View File

@@ -1,4 +1,5 @@
import type { IssuePriority, IssueStatus } from "../constants.js";
import type { ProjectWorkspace } from "./project.js";
export interface IssueAncestorProject {
id: string;
@@ -6,6 +7,8 @@ export interface IssueAncestorProject {
description: string | null;
status: string;
goalId: string | null;
workspaces: ProjectWorkspace[];
primaryWorkspace: ProjectWorkspace | null;
}
export interface IssueAncestorGoal {
@@ -31,6 +34,15 @@ export interface IssueAncestor {
goal: IssueAncestorGoal | null;
}
export interface IssueLabel {
id: string;
companyId: string;
name: string;
color: string;
createdAt: Date;
updatedAt: Date;
}
export interface Issue {
id: string;
companyId: string;
@@ -58,6 +70,8 @@ export interface Issue {
completedAt: Date | null;
cancelledAt: Date | null;
hiddenAt: Date | null;
labelIds?: string[];
labels?: IssueLabel[];
createdAt: Date;
updatedAt: Date;
}

View File

@@ -28,18 +28,24 @@ export {
export {
createProjectSchema,
updateProjectSchema,
createProjectWorkspaceSchema,
updateProjectWorkspaceSchema,
type CreateProject,
type UpdateProject,
type CreateProjectWorkspace,
type UpdateProjectWorkspace,
} from "./project.js";
export {
createIssueSchema,
createIssueLabelSchema,
updateIssueSchema,
checkoutIssueSchema,
addIssueCommentSchema,
linkIssueApprovalSchema,
createIssueAttachmentMetadataSchema,
type CreateIssue,
type CreateIssueLabel,
type UpdateIssue,
type CheckoutIssue,
type AddIssueComment,

View File

@@ -13,10 +13,18 @@ export const createIssueSchema = z.object({
assigneeUserId: z.string().optional().nullable(),
requestDepth: z.number().int().nonnegative().optional().default(0),
billingCode: z.string().optional().nullable(),
labelIds: z.array(z.string().uuid()).optional(),
});
export type CreateIssue = z.infer<typeof createIssueSchema>;
export const createIssueLabelSchema = z.object({
name: z.string().trim().min(1).max(48),
color: z.string().regex(/^#(?:[0-9a-fA-F]{6})$/, "Color must be a 6-digit hex value"),
});
export type CreateIssueLabel = z.infer<typeof createIssueLabelSchema>;
export const updateIssueSchema = createIssueSchema.partial().extend({
comment: z.string().min(1).optional(),
hiddenAt: z.string().datetime().nullable().optional(),