Files
kandev/apps/backend/internal/user/controller/controller.go
T
José Almeida 06e5114174 feat: add clickable terminal links with configurable open behavior (#401)
* feat: add clickable terminal links with configurable open behavior

Add @xterm/addon-web-links to both passthrough and shell terminals,
enabling Cmd+Click on URLs. Users can configure whether links open
in a new browser tab (default) or the built-in browser panel via a
new setting in the Settings page.

* style: fix prettier formatting

* fix: address CodeRabbit review comments

- Extract applyTerminalLinkBehavior helper to reduce complexity
- Set default terminalLinkBehavior for first-time users in empty settings path
- Use storeApi.getState() for optimistic rollback to avoid stale closures
- Add beforeEach settings reset in E2E tests
- Use 4xx range assertion instead of hardcoded 500

* fix: return 400 for user settings validation errors

Add ErrValidation sentinel to user service and wrap validation errors
from apply* functions. Handler now returns 400 Bad Request instead of
500 Internal Server Error for invalid input. Update E2E test assertion
to enforce client error status (< 500).

* fix: remove cmd+click mention from terminal links description

* fix: mirror validation error handling in WS update path

* style: fix prettier formatting
2026-03-10 15:37:51 +00:00

97 lines
3.0 KiB
Go

package controller
import (
"context"
"runtime"
"github.com/kandev/kandev/internal/user/dto"
"github.com/kandev/kandev/internal/user/service"
)
type Controller struct {
svc *service.Service
}
func NewController(svc *service.Service) *Controller {
return &Controller{svc: svc}
}
func (c *Controller) GetCurrentUser(ctx context.Context) (dto.UserResponse, error) {
user, err := c.svc.GetCurrentUser(ctx)
if err != nil {
return dto.UserResponse{}, err
}
settings, err := c.svc.GetUserSettings(ctx)
if err != nil {
return dto.UserResponse{}, err
}
return dto.UserResponse{
User: dto.FromUser(user),
Settings: dto.FromUserSettings(settings),
}, nil
}
func (c *Controller) GetUserSettings(ctx context.Context) (dto.UserSettingsResponse, error) {
settings, err := c.svc.GetUserSettings(ctx)
if err != nil {
return dto.UserSettingsResponse{}, err
}
return dto.UserSettingsResponse{
Settings: dto.FromUserSettings(settings),
ShellOptions: shellOptionsForOS(),
}, nil
}
func (c *Controller) UpdateUserSettings(ctx context.Context, req dto.UpdateUserSettingsRequest) (dto.UserSettingsResponse, error) {
settings, err := c.svc.UpdateUserSettings(ctx, &service.UpdateUserSettingsRequest{
WorkspaceID: req.WorkspaceID,
KanbanViewMode: req.KanbanViewMode,
WorkflowFilterID: req.WorkflowFilterID,
RepositoryIDs: req.RepositoryIDs,
InitialSetupComplete: req.InitialSetupComplete,
PreferredShell: req.PreferredShell,
DefaultEditorID: req.DefaultEditorID,
EnablePreviewOnClick: req.EnablePreviewOnClick,
ChatSubmitKey: req.ChatSubmitKey,
ReviewAutoMarkOnScroll: req.ReviewAutoMarkOnScroll,
ShowReleaseNotification: req.ShowReleaseNotification,
ReleaseNotesLastSeenVersion: req.ReleaseNotesLastSeenVersion,
LspAutoStartLanguages: req.LspAutoStartLanguages,
LspAutoInstallLanguages: req.LspAutoInstallLanguages,
LspServerConfigs: req.LspServerConfigs,
SavedLayouts: req.SavedLayouts,
DefaultUtilityAgentID: req.DefaultUtilityAgentID,
DefaultUtilityModel: req.DefaultUtilityModel,
KeyboardShortcuts: req.KeyboardShortcuts,
TerminalLinkBehavior: req.TerminalLinkBehavior,
})
if err != nil {
return dto.UserSettingsResponse{}, err
}
return dto.UserSettingsResponse{
Settings: dto.FromUserSettings(settings),
ShellOptions: shellOptionsForOS(),
}, nil
}
func shellOptionsForOS() []dto.ShellOption {
switch runtime.GOOS {
case "windows":
return []dto.ShellOption{
{Value: "auto", Label: "System default"},
{Value: "pwsh.exe", Label: "PowerShell (pwsh)"},
{Value: "powershell.exe", Label: "Windows PowerShell"},
{Value: "cmd.exe", Label: "Command Prompt"},
{Value: "custom", Label: "Custom"},
}
default:
return []dto.ShellOption{
{Value: "auto", Label: "System default"},
{Value: "/bin/zsh", Label: "zsh"},
{Value: "/bin/bash", Label: "bash"},
{Value: "/bin/sh", Label: "sh"},
{Value: "custom", Label: "Custom"},
}
}
}