Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
234 lines
9.8 KiB
Go
234 lines
9.8 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
|
|
storesqlite "github.com/tutti-os/tutti/packages/agent/store-sqlite"
|
|
tuttigenerated "github.com/tutti-os/tutti/services/tuttid/api/generated"
|
|
"github.com/tutti-os/tutti/services/tuttid/apierrors"
|
|
userprojectbiz "github.com/tutti-os/tutti/services/tuttid/biz/userproject"
|
|
userprojectservice "github.com/tutti-os/tutti/services/tuttid/service/userproject"
|
|
)
|
|
|
|
type UserProjectService interface {
|
|
CheckPath(context.Context, userprojectservice.CheckPathInput) (userprojectservice.PathCheck, error)
|
|
Delete(context.Context, userprojectservice.DeleteInput) error
|
|
List(context.Context) ([]userprojectbiz.Project, error)
|
|
Move(context.Context, userprojectservice.MoveInput) ([]userprojectbiz.Project, error)
|
|
Pin(context.Context, userprojectservice.PinInput) ([]userprojectbiz.Project, error)
|
|
Use(context.Context, userprojectservice.UseInput) (userprojectbiz.Project, error)
|
|
UseMany(context.Context, userprojectservice.UseManyInput) []error
|
|
}
|
|
|
|
func (api DaemonAPI) PinUserProject(ctx context.Context, request tuttigenerated.PinUserProjectRequestObject) (tuttigenerated.PinUserProjectResponseObject, error) {
|
|
if api.UserProjectService == nil {
|
|
return tuttigenerated.PinUserProject503JSONResponse{ServiceUnavailableErrorJSONResponse: userProjectServiceUnavailableError()}, nil
|
|
}
|
|
if request.Body == nil {
|
|
return tuttigenerated.PinUserProject400JSONResponse{InvalidRequestErrorJSONResponse: invalidRequestError(apierrors.EmptyBody(apierrors.WithDeveloperMessage("empty body")))}, nil
|
|
}
|
|
projects, err := api.UserProjectService.Pin(ctx, userprojectservice.PinInput{
|
|
ProjectID: request.Body.ProjectId,
|
|
Pinned: request.Body.Pinned,
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, userprojectservice.ErrInvalidArgument) {
|
|
return tuttigenerated.PinUserProject400JSONResponse{InvalidRequestErrorJSONResponse: invalidRequestError(apierrors.InvalidRequest(
|
|
apierrors.ReasonMalformedRequest,
|
|
apierrors.WithDeveloperMessage("user project pin references an unknown or invalid project"),
|
|
))}, nil
|
|
}
|
|
return tuttigenerated.PinUserProject502JSONResponse{PreferencesOperationErrorJSONResponse: preferencesOperationError(apierrors.PreferencesOperationFailed(apierrors.WithCause(err)))}, nil
|
|
}
|
|
return tuttigenerated.PinUserProject200JSONResponse{Projects: generatedUserProjects(projects)}, nil
|
|
}
|
|
|
|
func (api DaemonAPI) MoveUserProject(ctx context.Context, request tuttigenerated.MoveUserProjectRequestObject) (tuttigenerated.MoveUserProjectResponseObject, error) {
|
|
if api.UserProjectService == nil {
|
|
return tuttigenerated.MoveUserProject503JSONResponse{ServiceUnavailableErrorJSONResponse: userProjectServiceUnavailableError()}, nil
|
|
}
|
|
if request.Body == nil {
|
|
return tuttigenerated.MoveUserProject400JSONResponse{InvalidRequestErrorJSONResponse: invalidRequestError(apierrors.EmptyBody(apierrors.WithDeveloperMessage("empty body")))}, nil
|
|
}
|
|
projects, err := api.UserProjectService.Move(ctx, userprojectservice.MoveInput{
|
|
ProjectID: request.Body.ProjectId,
|
|
BeforeProjectID: request.Body.BeforeProjectId,
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, userprojectservice.ErrInvalidArgument) {
|
|
return tuttigenerated.MoveUserProject400JSONResponse{InvalidRequestErrorJSONResponse: invalidRequestError(apierrors.InvalidRequest(
|
|
apierrors.ReasonMalformedRequest,
|
|
apierrors.WithDeveloperMessage("user project move references an unknown or invalid project"),
|
|
))}, nil
|
|
}
|
|
return tuttigenerated.MoveUserProject502JSONResponse{PreferencesOperationErrorJSONResponse: preferencesOperationError(apierrors.PreferencesOperationFailed(apierrors.WithCause(err)))}, nil
|
|
}
|
|
return tuttigenerated.MoveUserProject200JSONResponse{Projects: generatedUserProjects(projects)}, nil
|
|
}
|
|
|
|
func (api DaemonAPI) CheckUserProjectPath(ctx context.Context, request tuttigenerated.CheckUserProjectPathRequestObject) (tuttigenerated.CheckUserProjectPathResponseObject, error) {
|
|
if api.UserProjectService == nil {
|
|
return tuttigenerated.CheckUserProjectPath503JSONResponse{
|
|
ServiceUnavailableErrorJSONResponse: userProjectServiceUnavailableError(),
|
|
}, nil
|
|
}
|
|
if request.Body == nil {
|
|
return tuttigenerated.CheckUserProjectPath400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(apierrors.EmptyBody(apierrors.WithDeveloperMessage("empty body"))),
|
|
}, nil
|
|
}
|
|
check, err := api.UserProjectService.CheckPath(ctx, userprojectservice.CheckPathInput{
|
|
Path: request.Body.Path,
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, userprojectservice.ErrInvalidArgument) {
|
|
return tuttigenerated.CheckUserProjectPath400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(
|
|
apierrors.InvalidRequest(
|
|
apierrors.ReasonInvalidPath,
|
|
apierrors.WithDeveloperMessage("user project path is invalid"),
|
|
apierrors.WithParams(map[string]any{"field": "path"}),
|
|
),
|
|
),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.CheckUserProjectPath502JSONResponse{
|
|
PreferencesOperationErrorJSONResponse: preferencesOperationError(
|
|
apierrors.PreferencesOperationFailed(apierrors.WithCause(err)),
|
|
),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.CheckUserProjectPath200JSONResponse{
|
|
Exists: check.Exists,
|
|
IsDirectory: check.IsDirectory,
|
|
Path: check.Path,
|
|
}, nil
|
|
}
|
|
|
|
func (api DaemonAPI) ListUserProjects(ctx context.Context, _ tuttigenerated.ListUserProjectsRequestObject) (tuttigenerated.ListUserProjectsResponseObject, error) {
|
|
if api.UserProjectService == nil {
|
|
return tuttigenerated.ListUserProjects503JSONResponse{
|
|
ServiceUnavailableErrorJSONResponse: userProjectServiceUnavailableError(),
|
|
}, nil
|
|
}
|
|
projects, err := api.UserProjectService.List(ctx)
|
|
if err != nil {
|
|
return tuttigenerated.ListUserProjects502JSONResponse{
|
|
PreferencesOperationErrorJSONResponse: preferencesOperationError(
|
|
apierrors.PreferencesOperationFailed(apierrors.WithCause(err)),
|
|
),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.ListUserProjects200JSONResponse{
|
|
Projects: generatedUserProjects(projects),
|
|
}, nil
|
|
}
|
|
|
|
func (api DaemonAPI) DeleteUserProject(ctx context.Context, request tuttigenerated.DeleteUserProjectRequestObject) (tuttigenerated.DeleteUserProjectResponseObject, error) {
|
|
if api.UserProjectService == nil {
|
|
return tuttigenerated.DeleteUserProject503JSONResponse{
|
|
ServiceUnavailableErrorJSONResponse: userProjectServiceUnavailableError(),
|
|
}, nil
|
|
}
|
|
if request.Body == nil {
|
|
return tuttigenerated.DeleteUserProject400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(apierrors.EmptyBody(apierrors.WithDeveloperMessage("empty body"))),
|
|
}, nil
|
|
}
|
|
err := api.UserProjectService.Delete(ctx, userprojectservice.DeleteInput{
|
|
Path: request.Body.Path,
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, userprojectservice.ErrInvalidArgument) {
|
|
return tuttigenerated.DeleteUserProject400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(
|
|
apierrors.InvalidRequest(
|
|
apierrors.ReasonInvalidPath,
|
|
apierrors.WithDeveloperMessage("user project path is invalid"),
|
|
apierrors.WithParams(map[string]any{"field": "path"}),
|
|
),
|
|
),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.DeleteUserProject502JSONResponse{
|
|
PreferencesOperationErrorJSONResponse: preferencesOperationError(
|
|
apierrors.PreferencesOperationFailed(apierrors.WithCause(err)),
|
|
),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.DeleteUserProject204Response{}, nil
|
|
}
|
|
|
|
func (api DaemonAPI) UseUserProject(ctx context.Context, request tuttigenerated.UseUserProjectRequestObject) (tuttigenerated.UseUserProjectResponseObject, error) {
|
|
if api.UserProjectService == nil {
|
|
return tuttigenerated.UseUserProject503JSONResponse{
|
|
ServiceUnavailableErrorJSONResponse: userProjectServiceUnavailableError(),
|
|
}, nil
|
|
}
|
|
if request.Body == nil {
|
|
return tuttigenerated.UseUserProject400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(apierrors.EmptyBody(apierrors.WithDeveloperMessage("empty body"))),
|
|
}, nil
|
|
}
|
|
project, err := api.UserProjectService.Use(ctx, userprojectservice.UseInput{
|
|
Path: request.Body.Path,
|
|
})
|
|
if err != nil {
|
|
if errors.Is(err, userprojectservice.ErrInvalidArgument) ||
|
|
errors.Is(err, userprojectservice.ErrNotDirectory) {
|
|
return tuttigenerated.UseUserProject400JSONResponse{
|
|
InvalidRequestErrorJSONResponse: invalidRequestError(
|
|
apierrors.InvalidRequest(
|
|
apierrors.ReasonInvalidPath,
|
|
apierrors.WithDeveloperMessage("user project path is invalid"),
|
|
apierrors.WithParams(map[string]any{"field": "path"}),
|
|
),
|
|
),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.UseUserProject502JSONResponse{
|
|
PreferencesOperationErrorJSONResponse: preferencesOperationError(
|
|
apierrors.PreferencesOperationFailed(apierrors.WithCause(err)),
|
|
),
|
|
}, nil
|
|
}
|
|
return tuttigenerated.UseUserProject201JSONResponse{
|
|
Project: generatedUserProject(project),
|
|
}, nil
|
|
}
|
|
|
|
func userProjectServiceUnavailableError() tuttigenerated.ServiceUnavailableErrorJSONResponse {
|
|
return serviceUnavailableError(
|
|
apierrors.PreferencesServiceUnavailable(
|
|
apierrors.WithDeveloperMessage("user project service is unavailable"),
|
|
),
|
|
)
|
|
}
|
|
|
|
func generatedUserProjects(projects []userprojectbiz.Project) []tuttigenerated.UserProject {
|
|
result := make([]tuttigenerated.UserProject, 0, len(projects))
|
|
for _, project := range projects {
|
|
result = append(result, generatedUserProject(project))
|
|
}
|
|
return result
|
|
}
|
|
|
|
func generatedUserProject(project userprojectbiz.Project) tuttigenerated.UserProject {
|
|
normalizedPath := storesqlite.NormalizeProjectPath(project.Path)
|
|
if normalizedPath == "" {
|
|
normalizedPath = strings.TrimSpace(project.Path)
|
|
}
|
|
return tuttigenerated.UserProject{
|
|
CreatedAtUnixMs: project.CreatedAtUnixMS,
|
|
Id: project.ID,
|
|
Label: project.Label,
|
|
LastUsedAtUnixMs: project.LastUsedAtUnixMS,
|
|
Path: normalizedPath,
|
|
PinnedAtUnixMs: project.PinnedAtUnixMS,
|
|
SectionKey: userprojectbiz.SectionKeyFromPath(normalizedPath),
|
|
UpdatedAtUnixMs: project.UpdatedAtUnixMS,
|
|
}
|
|
}
|