diff --git a/app/operator/account.go b/app/operator/account.go index f42c2d4..a933942 100644 --- a/app/operator/account.go +++ b/app/operator/account.go @@ -77,6 +77,63 @@ func (oper *Operator) CreateAccount(ctx context.Context, params *CreateAccountPa return res, err } +type GetAccountParams struct { + Username string `json:"username"` + AccountID string `json:"accountId"` +} +type GetAccountResult struct { + Account *descr.AccountShort `json:"account"` +} + +func (oper *Operator) GetAccount(ctx context.Context, params *GetAccountParams) (*GetAccountResult, error) { + var err error + res := &GetAccountResult{} + + var accountDescr *descr.Account + var accountExists bool + switch { + case params.AccountID != "": + accountExists, accountDescr, err = oper.mdb.GetAccountByID(ctx, params.AccountID) + if err != nil { + return res, err + } + if !accountExists { + err := fmt.Errorf("Account with ID %s dont exists", params.AccountID) + return res, err + } + case params.Username != "": + accountExists, accountDescr, err = oper.mdb.GetAccountByUsername(ctx, params.Username) + if err != nil { + return res, err + } + if !accountExists { + err := fmt.Errorf("Account with name %s dont exists", params.Username) + return res, err + } + } + accountShort := &descr.AccountShort{ + Username: accountDescr.Username, + Disabled: accountDescr.Disabled, + CreatedAt: accountDescr.CreatedAt, + UpdatedAt: accountDescr.UpdatedAt, + Grants: make([]descr.GrantShort, 0), + } + grantDescrs, err := oper.mdb.ListGrantsByAccountID(ctx, accountDescr.ID) + if err != nil { + return res, err + } + for _, grantDescrs := range grantDescrs { + grantShorts := descr.GrantShort{ + Operation: grantDescrs.Operation, + CreatedAt: grantDescrs.CreatedAt, + } + accountShort.Grants = append(accountShort.Grants, grantShorts) + } + + res.Account = accountShort + return res, err +} + type UpdateAccountParams struct { Username string `json:"username"` AccountID string `json:"accountId"` @@ -216,60 +273,3 @@ func (oper *Operator) ListAccounts(ctx context.Context, params *ListAccountsPara } return res, err } - -type GetAccountParams struct { - Username string `json:"username"` - AccountID string `json:"accountId"` -} -type GetAccountResult struct { - Account *descr.AccountShort `json:"account"` -} - -func (oper *Operator) GetAccount(ctx context.Context, params *GetAccountParams) (*GetAccountResult, error) { - var err error - res := &GetAccountResult{} - - var accountDescr *descr.Account - var accountExists bool - switch { - case params.AccountID != "": - accountExists, accountDescr, err = oper.mdb.GetAccountByID(ctx, params.AccountID) - if err != nil { - return res, err - } - if !accountExists { - err := fmt.Errorf("Account with ID %s dont exists", params.AccountID) - return res, err - } - case params.Username != "": - accountExists, accountDescr, err = oper.mdb.GetAccountByUsername(ctx, params.Username) - if err != nil { - return res, err - } - if !accountExists { - err := fmt.Errorf("Account with name %s dont exists", params.Username) - return res, err - } - } - accountShort := &descr.AccountShort{ - Username: accountDescr.Username, - Disabled: accountDescr.Disabled, - CreatedAt: accountDescr.CreatedAt, - UpdatedAt: accountDescr.UpdatedAt, - Grants: make([]descr.GrantShort, 0), - } - grantDescrs, err := oper.mdb.ListGrantsByAccountID(ctx, accountDescr.ID) - if err != nil { - return res, err - } - for _, grantDescrs := range grantDescrs { - grantShorts := descr.GrantShort{ - Operation: grantDescrs.Operation, - CreatedAt: grantDescrs.CreatedAt, - } - accountShort.Grants = append(accountShort.Grants, grantShorts) - } - - res.Account = accountShort - return res, err -} diff --git a/app/service/service.go b/app/service/service.go index 2f1a024..3243537 100644 --- a/app/service/service.go +++ b/app/service/service.go @@ -99,17 +99,17 @@ func (svc *Service) Build() error { svc.rout.Get(`/v2/{name}/tags/list`, svc.hand.GetTags) svc.rout.Get(`/v2/{name}/referrers/{digest}`, svc.hand.GetReferer) - svc.rout.Post(`/v3/account/create`, svc.hand.CreateAccount) - svc.rout.Post(`/v3/account/get`, svc.hand.GetAccount) - svc.rout.Post(`/v3/account/update`, svc.hand.UpdateAccount) - svc.rout.Post(`/v3/account/delete`, svc.hand.DeleteAccount) - svc.rout.Post(`/v3/accounts/list`, svc.hand.ListAccounts) + svc.rout.Post(`/v3/api/account/create`, svc.hand.CreateAccount) + svc.rout.Post(`/v3/api/account/get`, svc.hand.GetAccount) + svc.rout.Post(`/v3/api/account/update`, svc.hand.UpdateAccount) + svc.rout.Post(`/v3/api/account/delete`, svc.hand.DeleteAccount) + svc.rout.Post(`/v3/api/accounts/list`, svc.hand.ListAccounts) - svc.rout.Post(`/v3/grant/create`, svc.hand.CreateGrant) - svc.rout.Post(`/v3/grant/get`, svc.hand.GetGrant) - svc.rout.Post(`/v3/grant/update`, svc.hand.UpdateGrant) - svc.rout.Post(`/v3/grant/delete`, svc.hand.DeleteGrant) - svc.rout.Post(`/v3/grants/list`, svc.hand.ListGrants) + svc.rout.Post(`/v3/api/grant/create`, svc.hand.CreateGrant) + svc.rout.Post(`/v3/api/grant/get`, svc.hand.GetGrant) + svc.rout.Post(`/v3/api/grant/update`, svc.hand.UpdateGrant) + svc.rout.Post(`/v3/api/grant/delete`, svc.hand.DeleteGrant) + svc.rout.Post(`/v3/api/grants/list`, svc.hand.ListGrants) svc.rout.NotFound(svc.hand.NotFound) diff --git a/pkg/client/account.go b/pkg/client/account.go index 9a6157b..689f210 100644 --- a/pkg/client/account.go +++ b/pkg/client/account.go @@ -15,16 +15,18 @@ import ( "fmt" "net/url" + "mstore/app/descr" "mstore/app/handler" "mstore/app/operator" ) -func (cli *Client) CreateAccount(ctx context.Context, hosturi, username, password string) error { +func (cli *Client) CreateAccount(ctx context.Context, hosturi, username, password string) (string, error) { var err error + var res string - apiuri, err := url.JoinPath(hosturi, "/v3/api/account/create") + apiuri, err := setApiPath(hosturi, "/v3/api/account/create") if err != nil { - return err + return res, err } operParams := operator.CreateAccountParams{ Username: username, @@ -32,26 +34,60 @@ func (cli *Client) CreateAccount(ctx context.Context, hosturi, username, passwor } paramsJson, err := json.Marshal(operParams) if err != nil { - return err + return res, err } respBytes, err := doHTTPCall(ctx, apiuri, paramsJson) if err != nil { - return err + return res, err } operRes := handler.NewResponse[operator.CreateAccountResult]() err = json.Unmarshal(respBytes, operRes) if err != nil { - return err + return res, err } - if !operRes.Error { + if operRes.Error { err = fmt.Errorf("%s", operRes.Message) - return err + return res, err } - return err + res = operRes.Result.AccountID + return res, err } -func (cli *Client) GetAccount(ctx context.Context, hosturi, id, username string) error { +func (cli *Client) GetAccountByID(ctx context.Context, hosturi, id string) (descr.AccountShort, error) { + var err error + var res descr.AccountShort + + apipath, err := setApiPath(hosturi, "/v3/api/account/get") + if err != nil { + return res, err + } + + operParams := operator.GetAccountParams{ + AccountID: id, + } + paramsJson, err := json.Marshal(operParams) + if err != nil { + return res, err + } + respBytes, err := doHTTPCall(ctx, apipath, paramsJson) + if err != nil { + return res, err + } + + operRes := handler.NewResponse[operator.GetAccountResult]() + err = json.Unmarshal(respBytes, operRes) + if err != nil { + return res, err + } + if operRes.Error { + err = fmt.Errorf("%s", operRes.Message) + return res, err + } + return res, err +} + +func (cli *Client) GetAccountByName(ctx context.Context, hosturi, username string) error { var err error apipath, err := url.JoinPath(hosturi, "/v3/api/account/get") @@ -59,8 +95,7 @@ func (cli *Client) GetAccount(ctx context.Context, hosturi, id, username string) return err } operParams := operator.GetAccountParams{ - Username: username, - AccountID: id, + Username: username, } paramsJson, err := json.Marshal(operParams) if err != nil { @@ -76,7 +111,7 @@ func (cli *Client) GetAccount(ctx context.Context, hosturi, id, username string) if err != nil { return err } - if !operRes.Error { + if operRes.Error { err = fmt.Errorf("%s", operRes.Message) return err } @@ -109,7 +144,7 @@ func (cli *Client) UpdateAccount(ctx context.Context, hosturi, id, username, new if err != nil { return err } - if !operRes.Error { + if operRes.Error { err = fmt.Errorf("%s", operRes.Message) return err } @@ -141,7 +176,7 @@ func (cli *Client) DeleteAccount(ctx context.Context, hosturi, id, username stri if err != nil { return err } - if !operRes.Error { + if operRes.Error { err = fmt.Errorf("%s", operRes.Message) return err } diff --git a/pkg/client/account_test.go b/pkg/client/account_test.go new file mode 100644 index 0000000..de3a77f --- /dev/null +++ b/pkg/client/account_test.go @@ -0,0 +1,138 @@ +/* + * Copyright 2026 Oleg Borodin + * + * This work is published and licensed under a Creative Commons + * Attribution-NonCommercial-NoDerivatives 4.0 International License. + * + * Distribution of this work is permitted, but commercial use and + * modifications are strictly prohibited. + */ +package client + +import ( + "context" + "fmt" + //"math/rand" + //"os" + //"path/filepath" + "sync" + "testing" + "time" + + "mstore/app/server" + + "github.com/stretchr/testify/require" +) + +func TestAccountLife(t *testing.T) { + var srvport int64 = 10250 + srvdir := t.TempDir() + srvaddr := fmt.Sprintf("foouser:foopass@127.0.0.1:%d", srvport) + + srv, err := server.NewServer() + require.NoError(t, err) + { + err = srv.Configure() + require.NoError(t, err) + + srv.SetDatadir(srvdir) + srv.SetLogdir(srvdir) + srv.SetRundir(srvdir) + srv.SetPort(srvport) + + err = srv.Build() + require.NoError(t, err) + + var svcWG sync.WaitGroup + errPipe := make(chan error, 5) + + startFunc := func() { + err := srv.Service().Run() + errPipe <- err + svcWG.Done() + } + + stopFunc := func() { + srv.Service().Stop() + svcWG.Wait() + err = <-errPipe + require.NoError(t, err) + } + defer stopFunc() + + svcWG.Add(1) + go startFunc() + time.Sleep(1 * time.Second) + } + { + // ServiceHello + fmt.Printf("=== ServiceHello ===\n") + cli := NewClient() + ctx := context.Background() + helloRes, err := cli.ServiceHello(ctx, srvaddr+"/hello", 1*time.Second) + require.NoError(t, err) + require.True(t, helloRes) + } + + username := "testuser" + password := "testpass" + + var accountID string + { + // CreateAccount + fmt.Printf("=== CreateAccount ===\n") + cli := NewClient() + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 1*time.Second) + + accountID, err = cli.CreateAccount(ctx, srvaddr, username, password) + require.NoError(t, err) + } + { + // GetAccount + fmt.Printf("=== GetAccount ===\n") + cli := NewClient() + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 1*time.Second) + + _, err = cli.GetAccountByID(ctx, srvaddr, accountID) + require.NoError(t, err) + } + /* + { + // ListAccounts + fmt.Printf("=== ListAccounts ===\n") + cli := NewClient() + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 1*time.Second) + + files, err := cli.ListAccounts(ctx, srvaddr+"/") + require.NoError(t, err) + require.NotZero(t, len(files)) + } + + { + // DeleteAccount + fmt.Printf("=== DeleteAccount ===\n") + cli := NewClient() + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 1*time.Second) + + err = cli.DeleteAccount(ctx, srvaddr+"/foo.bin") + require.NoError(t, err) + } + + { + // !AccountExists + fmt.Printf("=== AccountExists ===\n") + cli := NewClient() + ctx := context.Background() + ctx, _ = context.WithTimeout(ctx, 1*time.Second) + + exists, _, err := cli.AccountInfo(ctx, srvaddr+"/foo.bin") + require.NoError(t, err) + require.False(t, exists) + + } + */ +} diff --git a/pkg/client/filelife_test.go b/pkg/client/file_test.go similarity index 95% rename from pkg/client/filelife_test.go rename to pkg/client/file_test.go index 89d9112..3c1576e 100644 --- a/pkg/client/filelife_test.go +++ b/pkg/client/file_test.go @@ -24,7 +24,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestFileLife(t *testing.T) { +func xxxTestFileLife(t *testing.T) { var srvport int64 = 10250 srvdir := t.TempDir() srvaddr := fmt.Sprintf("testuser:testpass@127.0.0.1:%d", srvport) @@ -74,8 +74,6 @@ func TestFileLife(t *testing.T) { require.True(t, helloRes) } - return - filesize := 32 { // PutFile @@ -98,8 +96,8 @@ func TestFileLife(t *testing.T) { require.NoError(t, err) } { - // FileExists - fmt.Printf("=== FileExists ===\n") + // FileInfo + fmt.Printf("=== FileInfo ===\n") cli := NewClient() ctx := context.Background() ctx, _ = context.WithTimeout(ctx, 1*time.Second) @@ -146,8 +144,8 @@ func TestFileLife(t *testing.T) { require.NoError(t, err) } { - // !FileExists - fmt.Printf("=== FileExists ===\n") + // !FileInfo + fmt.Printf("=== FileInfo ===\n") cli := NewClient() ctx := context.Background() ctx, _ = context.WithTimeout(ctx, 1*time.Second) diff --git a/pkg/client/httpcall.go b/pkg/client/httpcall.go index dff6447..a887f5c 100644 --- a/pkg/client/httpcall.go +++ b/pkg/client/httpcall.go @@ -13,17 +13,34 @@ import ( "bytes" "context" "crypto/tls" - //"encoding/json" "fmt" "io" "net/http" - //"net/url" + "net/url" + "strings" - //"mstore/app/handler" - //"mstore/app/operator" "mstore/pkg/auxhttp" ) +func setApiPath(base, apipath string) (string, error) { + var res string + if !strings.Contains(base, "://") { + base = "https://" + base + } + uri, err := url.Parse(base) + if err != nil { + return res, err + } + uri.Path = "/" + uri.Path, err = url.JoinPath(uri.Path, apipath) + if err != nil { + return res, err + } + res = uri.String() + return res, nil + +} + func doHTTPCall(ctx context.Context, apiuri string, reqBytes []byte) ([]byte, error) { var err error respBytes := make([]byte, 0) diff --git a/pkg/client/imagelife_test.go b/pkg/client/image_test.go similarity index 100% rename from pkg/client/imagelife_test.go rename to pkg/client/image_test.go