Files
agent-radar 2ed3415fb7
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
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

252 lines
10 KiB
Go

package api
import (
"context"
"errors"
"time"
authbridge "github.com/tutti-os/tutti/packages/auth/bridge-go"
"github.com/tutti-os/tutti/packages/commerce"
tuttigenerated "github.com/tutti-os/tutti/services/tuttid/api/generated"
"github.com/tutti-os/tutti/services/tuttid/apierrors"
accountservice "github.com/tutti-os/tutti/services/tuttid/service/account"
)
type AccountService interface {
DismissRegistrationCreditsReward(context.Context, string) error
GetProductSummary(context.Context) (accountservice.ProductSummary, error)
GetUserInfo(context.Context) (*authbridge.UserInfo, error)
LoginStatus(string) (authbridge.LoginStatus, error)
Logout(context.Context) error
StartLogin(context.Context) (accountservice.LoginStart, error)
}
func (api DaemonAPI) StartAccountLogin(ctx context.Context, _ tuttigenerated.StartAccountLoginRequestObject) (tuttigenerated.StartAccountLoginResponseObject, error) {
if api.AccountService == nil {
return tuttigenerated.StartAccountLogin503JSONResponse{ServiceUnavailableErrorJSONResponse: accountServiceUnavailableError()}, nil
}
start, err := api.AccountService.StartLogin(ctx)
if err != nil {
return tuttigenerated.StartAccountLogin503JSONResponse{
ServiceUnavailableErrorJSONResponse: serviceUnavailableError(
apierrors.ServiceUnavailable("account_login_failed", apierrors.WithCause(err)),
),
}, nil
}
return tuttigenerated.StartAccountLogin200JSONResponse{
AttemptId: start.AttemptID,
ExpiresAt: start.ExpiresAt,
LoginUrl: start.LoginURL,
}, nil
}
func (api DaemonAPI) GetAccountLoginStatus(_ context.Context, request tuttigenerated.GetAccountLoginStatusRequestObject) (tuttigenerated.GetAccountLoginStatusResponseObject, error) {
if api.AccountService == nil {
return tuttigenerated.GetAccountLoginStatus503JSONResponse{ServiceUnavailableErrorJSONResponse: accountServiceUnavailableError()}, nil
}
status, err := api.AccountService.LoginStatus(request.Params.AttemptId)
if err != nil {
if errors.Is(err, accountservice.ErrAttemptNotFound) {
return tuttigenerated.GetAccountLoginStatus400JSONResponse{
InvalidRequestErrorJSONResponse: invalidRequestError(
apierrors.InvalidRequest("account_login_attempt_not_found", apierrors.WithParams(map[string]any{"field": "attempt_id"})),
),
}, nil
}
return tuttigenerated.GetAccountLoginStatus503JSONResponse{
ServiceUnavailableErrorJSONResponse: serviceUnavailableError(
apierrors.ServiceUnavailable("account_login_status_failed", apierrors.WithCause(err)),
),
}, nil
}
return tuttigenerated.GetAccountLoginStatus200JSONResponse{
AttemptId: status.AttemptID,
Error: stringPointer(status.Error),
ExpiresAt: status.ExpiresAt.UnixMilli(),
Status: tuttigenerated.AccountLoginStatusValue(status.Status),
User: generatedAccountUser(status.User),
}, nil
}
func (api DaemonAPI) GetAccountUserInfo(ctx context.Context, _ tuttigenerated.GetAccountUserInfoRequestObject) (tuttigenerated.GetAccountUserInfoResponseObject, error) {
if api.AccountService == nil {
return tuttigenerated.GetAccountUserInfo503JSONResponse{ServiceUnavailableErrorJSONResponse: accountServiceUnavailableError()}, nil
}
user, err := api.AccountService.GetUserInfo(ctx)
if err != nil {
return tuttigenerated.GetAccountUserInfo503JSONResponse{
ServiceUnavailableErrorJSONResponse: serviceUnavailableError(
apierrors.ServiceUnavailable("account_user_info_failed", apierrors.WithCause(err)),
),
}, nil
}
return tuttigenerated.GetAccountUserInfo200JSONResponse{
User: generatedAccountUser(user),
}, nil
}
func (api DaemonAPI) GetAccountProductSummary(ctx context.Context, _ tuttigenerated.GetAccountProductSummaryRequestObject) (tuttigenerated.GetAccountProductSummaryResponseObject, error) {
if api.AccountService == nil {
return tuttigenerated.GetAccountProductSummary503JSONResponse{ServiceUnavailableErrorJSONResponse: accountServiceUnavailableError()}, nil
}
summary, err := api.AccountService.GetProductSummary(ctx)
if err != nil {
return tuttigenerated.GetAccountProductSummary503JSONResponse{
ServiceUnavailableErrorJSONResponse: serviceUnavailableError(
apierrors.ServiceUnavailable("account_product_summary_failed", apierrors.WithCause(err)),
),
}, nil
}
return tuttigenerated.GetAccountProductSummary200JSONResponse{
User: generatedAccountUser(summary.User),
Membership: generatedAccountMembership(summary.Membership),
MembershipAccess: generatedAccountMembershipAccess(summary.MembershipAccess),
Credits: generatedAccountCredits(summary.Credits),
RegistrationCreditsReward: generatedAccountRegistrationCreditsReward(summary.RegistrationCreditsReward),
PartialError: generatedAccountProductPartialError(summary.PartialError),
Links: tuttigenerated.AccountProductSummaryLinks{
PlanUrl: summary.Links.PlanURL,
UsageUrl: summary.Links.UsageURL,
SettingsUrl: summary.Links.SettingsURL,
},
}, nil
}
func generatedAccountMembershipAccess(
access commerce.MembershipAccessState,
) tuttigenerated.AccountMembershipAccessState {
switch access {
case commerce.MembershipAccessFree,
commerce.MembershipAccessActive,
commerce.MembershipAccessInactive:
return tuttigenerated.AccountMembershipAccessState(access)
default:
return tuttigenerated.AccountMembershipAccessStateUnknown
}
}
func (api DaemonAPI) DismissAccountRegistrationCreditsReward(ctx context.Context, request tuttigenerated.DismissAccountRegistrationCreditsRewardRequestObject) (tuttigenerated.DismissAccountRegistrationCreditsRewardResponseObject, error) {
if api.AccountService == nil {
return tuttigenerated.DismissAccountRegistrationCreditsReward503JSONResponse{ServiceUnavailableErrorJSONResponse: accountServiceUnavailableError()}, nil
}
if request.Body == nil {
return tuttigenerated.DismissAccountRegistrationCreditsReward400JSONResponse{
InvalidRequestErrorJSONResponse: invalidRequestError(apierrors.EmptyBody(apierrors.WithDeveloperMessage("empty body"))),
}, nil
}
if err := api.AccountService.DismissRegistrationCreditsReward(ctx, request.Body.RewardId); err != nil {
if errors.Is(err, accountservice.ErrRegistrationCreditsRewardIDRequired) {
return tuttigenerated.DismissAccountRegistrationCreditsReward400JSONResponse{
InvalidRequestErrorJSONResponse: invalidRequestError(
apierrors.InvalidRequest("account_registration_credits_reward_id_required", apierrors.WithParams(map[string]any{"field": "reward_id"})),
),
}, nil
}
return tuttigenerated.DismissAccountRegistrationCreditsReward503JSONResponse{
ServiceUnavailableErrorJSONResponse: serviceUnavailableError(
apierrors.ServiceUnavailable("account_registration_credits_reward_dismiss_failed", apierrors.WithCause(err)),
),
}, nil
}
return tuttigenerated.DismissAccountRegistrationCreditsReward204Response{}, nil
}
func (api DaemonAPI) LogoutAccount(ctx context.Context, _ tuttigenerated.LogoutAccountRequestObject) (tuttigenerated.LogoutAccountResponseObject, error) {
if api.AccountService == nil {
return tuttigenerated.LogoutAccount503JSONResponse{ServiceUnavailableErrorJSONResponse: accountServiceUnavailableError()}, nil
}
if err := api.AccountService.Logout(ctx); err != nil {
return tuttigenerated.LogoutAccount503JSONResponse{
ServiceUnavailableErrorJSONResponse: serviceUnavailableError(
apierrors.ServiceUnavailable("account_logout_failed", apierrors.WithCause(err)),
),
}, nil
}
return tuttigenerated.LogoutAccount204Response{}, nil
}
func accountServiceUnavailableError() tuttigenerated.ServiceUnavailableErrorJSONResponse {
return serviceUnavailableError(
apierrors.ServiceUnavailable("account_service_unavailable", apierrors.WithDeveloperMessage("account service is unavailable")),
)
}
func generatedAccountUser(user *authbridge.UserInfo) *tuttigenerated.AccountUserInfo {
if user == nil || user.UserID == "" {
return nil
}
return &tuttigenerated.AccountUserInfo{
Avatar: stringPointer(user.Avatar),
Email: stringPointer(user.Email),
Name: stringPointer(user.Name),
UserId: user.UserID,
}
}
func generatedAccountMembership(membership *accountservice.MembershipSummary) *tuttigenerated.AccountMembershipSummary {
if membership == nil || membership.TierKey == "" {
return nil
}
return &tuttigenerated.AccountMembershipSummary{
TierKey: membership.TierKey,
DisplayName: membership.DisplayName,
BillingPeriod: stringPointer(membership.BillingPeriod),
Status: stringPointer(membership.Status),
AccessStatus: stringPointer(membership.AccessStatus),
CurrentPeriodEnd: stringPointer(membership.CurrentPeriodEnd),
CancelAtPeriodEnd: membership.CancelAtPeriodEnd,
}
}
func generatedAccountCredits(credits *accountservice.CreditsSummary) *tuttigenerated.AccountCreditsSummary {
if credits == nil {
return nil
}
return &tuttigenerated.AccountCreditsSummary{
AvailableCredits: credits.AvailableCredits,
ExpiringCreditsWithin24h: credits.ExpiringCreditsWithin24h,
NextExpireAt: stringPointer(credits.NextExpireAt),
RefreshedAt: stringPointer(credits.RefreshedAt),
}
}
func generatedAccountRegistrationCreditsReward(reward *accountservice.RegistrationCreditsReward) *tuttigenerated.AccountRegistrationCreditsReward {
if reward == nil || reward.ID == "" || reward.GrantNo == "" || reward.Credits <= 0 {
return nil
}
createdAt := reward.CreatedAt
if createdAt.IsZero() {
createdAt = time.Now().UTC()
}
return &tuttigenerated.AccountRegistrationCreditsReward{
Id: reward.ID,
GrantNo: reward.GrantNo,
Credits: reward.Credits,
CreatedAt: createdAt.UTC().Format(time.RFC3339Nano),
}
}
func generatedAccountProductPartialError(partialError *accountservice.ProductSummaryPartialError) *tuttigenerated.AccountProductSummaryPartialError {
if partialError == nil || partialError.Code == "" {
return nil
}
return &tuttigenerated.AccountProductSummaryPartialError{
Scope: generatedAccountProductPartialErrorScope(partialError.Scope),
Code: partialError.Code,
Message: stringPointer(partialError.Message),
}
}
func generatedAccountProductPartialErrorScope(scope string) tuttigenerated.AccountProductSummaryPartialErrorScope {
switch scope {
case "membership":
return tuttigenerated.AccountProductSummaryPartialErrorScopeMembership
case "credits":
return tuttigenerated.AccountProductSummaryPartialErrorScopeCredits
case "links":
return tuttigenerated.AccountProductSummaryPartialErrorScopeLinks
default:
return tuttigenerated.AccountProductSummaryPartialErrorScopeUnknown
}
}