From 4565e18e36720e11035c678e68c59297a7861810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9E=D0=BB=D0=B5=D0=B3=20=D0=91=D0=BE=D1=80=D0=BE=D0=B4?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Sat, 7 Feb 2026 14:11:44 +0200 Subject: [PATCH] working commit --- app/handler/account.go | 104 ++++++++++++++++ app/operator/account.go | 265 ++++++++++++++++++++++++++++++++++++++++ pkg/client/file.go | 209 +++++++++++++++++++++++++++++++ 3 files changed, 578 insertions(+) create mode 100644 app/handler/account.go create mode 100644 app/operator/account.go create mode 100644 pkg/client/file.go diff --git a/app/handler/account.go b/app/handler/account.go new file mode 100644 index 0000000..d8bfdbf --- /dev/null +++ b/app/handler/account.go @@ -0,0 +1,104 @@ +package handler + +import ( + //"fmt" + + //"mstore/app/descr" + "mstore/app/operator" + "mstore/app/router" +) + +// POST /v3/account/create 200 200 +func (hand *Handler) CreateAccount(rctx *router.Context) { + var err error + + params := &operator.CreateAccountParams{} + err = rctx.BindJSON(params) + if err != nil { + hand.SendError(rctx, err) + return + } + res, err := hand.oper.CreateAccount(rctx.Ctx, params) + if err != nil { + hand.logg.Errorf("CreateAccount error: %v", err) + hand.SendError(rctx, err) + return + } + hand.SendResult(rctx, res) +} + +// POST /v3/account/get 200 200 +func (hand *Handler) GetAccount(rctx *router.Context) { + var err error + + params := &operator.GetAccountParams{} + err = rctx.BindJSON(params) + if err != nil { + hand.SendError(rctx, err) + return + } + res, err := hand.oper.GetAccount(rctx.Ctx, params) + if err != nil { + hand.logg.Errorf("CreateAccount error: %v", err) + hand.SendError(rctx, err) + return + } + hand.SendResult(rctx, res) +} + +// POST /v3/accounts/list 200 200 +func (hand *Handler) ListAccounts(rctx *router.Context) { + var err error + + params := &operator.ListAccountsParams{} + err = rctx.BindJSON(params) + if err != nil { + hand.SendError(rctx, err) + return + } + res, err := hand.oper.ListAccounts(rctx.Ctx, params) + if err != nil { + hand.logg.Errorf("ListAccounts error: %v", err) + hand.SendError(rctx, err) + return + } + hand.SendResult(rctx, res) +} + +// POST /v3/account/get 200 200 +func (hand *Handler) UpdateAccount(rctx *router.Context) { + var err error + + params := &operator.UpdateAccountParams{} + err = rctx.BindJSON(params) + if err != nil { + hand.SendError(rctx, err) + return + } + res, err := hand.oper.UpdateAccount(rctx.Ctx, params) + if err != nil { + hand.logg.Errorf("UpdateAccount error: %v", err) + hand.SendError(rctx, err) + return + } + hand.SendResult(rctx, res) +} + +// POST /v3/account/delete 200 200 +func (hand *Handler) DeleteAccount(rctx *router.Context) { + var err error + + params := &operator.DeleteAccountParams{} + err = rctx.BindJSON(params) + if err != nil { + hand.SendError(rctx, err) + return + } + res, err := hand.oper.DeleteAccount(rctx.Ctx, params) + if err != nil { + hand.logg.Errorf("DeleteAccount error: %v", err) + hand.SendError(rctx, err) + return + } + hand.SendResult(rctx, res) +} diff --git a/app/operator/account.go b/app/operator/account.go new file mode 100644 index 0000000..194aecf --- /dev/null +++ b/app/operator/account.go @@ -0,0 +1,265 @@ +package operator + +import ( + "context" + "fmt" + + "mstore/app/descr" + "mstore/pkg/auxpwd" + "mstore/pkg/auxtool" + "mstore/pkg/auxuuid" +) + +func (oper *Operator) ValidateAcount(ctx context.Context, username, password string) (bool, string, error) { + var err error + var accountID string + valid := false + + //oper.WaitRestoring() + + accountExists, accountDescr, err := oper.mdb.GetAccountByUsername(ctx, username) + if !accountExists { + err := fmt.Errorf("Account not exists") + return valid, accountID, err + } + if !auxpwd.PasswordMatch([]byte(password), accountDescr.Passhash) { + err := fmt.Errorf("Login data mismatch") + return valid, accountID, err + } + valid = true + accountID = accountDescr.ID + return valid, accountID, err +} + +type CreateAccountParams struct { + Username string + Password string +} +type CreateAccountResult struct { + AccountID string `json:"accountId"` +} + +func (oper *Operator) CreateAccount(ctx context.Context, params *CreateAccountParams) (*CreateAccountResult, error) { + var err error + res := &CreateAccountResult{} + + if params.Username == "" { + err := fmt.Errorf("Empty username parameters") + return res, err + } + + if params.Password == "" { + err := fmt.Errorf("Empty password parameter") + return res, err + } + + accountExists, _, err := oper.mdb.GetAccountByUsername(ctx, params.Username) + if err != nil { + return res, err + } + if accountExists { + err := fmt.Errorf("Account with thist name already exists") + return res, err + } + now := auxtool.TimeNow() + passhash := auxpwd.MakeSHA256Hash([]byte(params.Password)) + accountDescr := &descr.Account{ + ID: auxuuid.NewUUID(), + Username: params.Username, + Passhash: passhash, + Disabled: false, + CreatedAt: now, + UpdatedAt: now, + } + err = oper.mdb.InsertAccount(ctx, accountDescr) + if err != nil { + return res, err + } + res.AccountID = accountDescr.ID + return res, err +} + +type UpdateAccountParams struct { + Username string + AccountID string + NewUsername string + NewPassword string + Disabled bool +} +type UpdateAccountResult struct{} + +func (oper *Operator) UpdateAccount(ctx context.Context, params *UpdateAccountParams) (*UpdateAccountResult, error) { + var err error + res := &UpdateAccountResult{} + + 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 + } + 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 this is or name dont exists") + return res, err + } + now := auxtool.TimeNow() + if params.NewUsername != "" { + accountDescr.UpdatedAt = now + accountDescr.Username = params.NewUsername + } + if params.NewPassword != "" { + accountDescr.UpdatedAt = now + passhash := auxpwd.MakeSHA256Hash([]byte(params.NewPassword)) + accountDescr.Passhash = passhash + } + if params.Disabled != accountDescr.Disabled { + accountDescr.UpdatedAt = now + accountDescr.Disabled = params.Disabled + } + + err = oper.mdb.UpdateAccountByID(ctx, accountDescr.ID, accountDescr) + if err != nil { + return res, err + } + return res, err +} + +type DeleteAccountParams struct { + Username string + AccountID string +} +type DeleteAccountResult struct{} + +func (oper *Operator) DeleteAccount(ctx context.Context, params *DeleteAccountParams) (*DeleteAccountResult, error) { + var err error + res := &DeleteAccountResult{} + + 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 + } + 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 this is or name dont exists") + return res, err + } + + err = oper.mdb.DeleteAllGrantsForAccountID(ctx, accountDescr.ID) + if err != nil { + return res, err + } + err = oper.mdb.DeleteAccountByID(ctx, accountDescr.ID) + if err != nil { + return res, err + } + return res, err +} + +type ListAccountsParams struct{} +type ListAccountsResult struct { + Accounts []descr.AccountShortDescr `json:"accounts"` +} + +func (oper *Operator) ListAccounts(ctx context.Context, params *ListAccountsParams) (*ListAccountsResult, error) { + var err error + res := &ListAccountsResult{ + Accounts: make([]descr.AccountShortDescr, 0), + } + + accountDescrs, err := oper.mdb.ReducedListAccounts(ctx) + if err != nil { + return res, err + } + for _, accountDescr := range accountDescrs { + accountShortDescr := descr.AccountShortDescr{ + Username: accountDescr.Username, + Disabled: accountDescr.Disabled, + CreatedAt: accountDescr.CreatedAt, + UpdatedAt: accountDescr.UpdatedAt, + Grants: make([]descr.GrantShortDescr, 0), + } + grantDescrs, err := oper.mdb.ListGrantsByAccountID(ctx, accountDescr.ID) + if err != nil { + return res, err + } + for _, grantDescrs := range grantDescrs { + grantShortDescrs := descr.GrantShortDescr{ + Operation: grantDescrs.Operation, + CreatedAt: grantDescrs.CreatedAt, + } + accountShortDescr.Grants = append(accountShortDescr.Grants, grantShortDescrs) + } + res.Accounts = append(res.Accounts, accountShortDescr) + } + return res, err +} + +type GetAccountParams struct { + Username string + AccountID string +} +type GetAccountResult struct { + AccountDescr *descr.AccountShortDescr +} + +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.Username) + if err != nil { + 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 this is or name dont exists") + return res, err + } + accountShortDescr := &descr.AccountShortDescr{ + Username: accountDescr.Username, + Disabled: accountDescr.Disabled, + CreatedAt: accountDescr.CreatedAt, + UpdatedAt: accountDescr.UpdatedAt, + Grants: make([]descr.GrantShortDescr, 0), + } + grantDescrs, err := oper.mdb.ListGrantsByAccountID(ctx, accountDescr.ID) + if err != nil { + return res, err + } + for _, grantDescrs := range grantDescrs { + grantShortDescrs := descr.GrantShortDescr{ + Operation: grantDescrs.Operation, + CreatedAt: grantDescrs.CreatedAt, + } + accountShortDescr.Grants = append(accountShortDescr.Grants, grantShortDescrs) + } + + res.AccountDescr = accountShortDescr + return res, err +} diff --git a/pkg/client/file.go b/pkg/client/file.go new file mode 100644 index 0000000..717ff19 --- /dev/null +++ b/pkg/client/file.go @@ -0,0 +1,209 @@ +/* + * 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" + "crypto/tls" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strconv" +) + +func (cli *Client) FileExists(ctx context.Context, ref string) (bool, error) { + var res bool + var err error + + ref, err = convertFileRefer(ref) + if err != nil { + return res, err + } + req, err := http.NewRequestWithContext(ctx, http.MethodHead, ref, nil) + if err != nil { + return res, err + } + + if cli.username != "" && cli.password != "" { + req.Header.Add("Authorization", encodeBasicAuth(cli.username, cli.password)) + } + + transport := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + client := &http.Client{ + Transport: transport, + } + resp, err := client.Do(req) + if err != nil { + return res, err + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusOK { + res = true + } + return res, err +} + +func (cli *Client) PutFile(ctx context.Context, filename, ref string) error { + var err error + ref, err = convertFileRefer(ref) + if err != nil { + return err + } + file, err := os.Open(filename) + if err != nil { + return err + } + defer file.Close() + + req, err := http.NewRequestWithContext(ctx, http.MethodPut, ref, file) + if err != nil { + return err + } + + if cli.username != "" && cli.password != "" { + req.Header.Add("Authorization", encodeBasicAuth(cli.username, cli.password)) + } + + transport := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + client := &http.Client{ + Transport: transport, + } + fileinfo, err := os.Stat(filename) + if err != nil { + return err + } + filesize := fileinfo.Size() + + req.ContentLength = filesize + req.Header.Set("Content-Type", "application/octet-stream") + + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + err := fmt.Errorf("Received wrong status code: %s", resp.Status) + return err + } + return err +} + +func (cli *Client) GetFile(ctx context.Context, ref, filename string) (int64, error) { + var err error + var size int64 + ref, err = convertFileRefer(ref) + if err != nil { + return size, err + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, ref, nil) + if err != nil { + return size, err + } + + if cli.username != "" && cli.password != "" { + req.Header.Add("Authorization", encodeBasicAuth(cli.username, cli.password)) + } + + transport := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + client := &http.Client{ + Transport: transport, + } + resp, err := client.Do(req) + if err != nil { + return size, err + } + defer resp.Body.Close() + + contentLength := resp.Header.Get("Content-Length") + if contentLength == "" { + err = fmt.Errorf("Empty Content-Length received") + return size, err + } + if resp.StatusCode != http.StatusOK { + err := fmt.Errorf("Received wrong status code: %s", resp.Status) + return size, err + } + declSize, err := strconv.ParseInt(contentLength, 10, 64) + if err != nil { + err = fmt.Errorf("Wrong Content-Length value: %v", err) + return size, err + } + dirname := filepath.Dir(filename) + err = os.MkdirAll(dirname, 0750) + if err != nil { + return size, err + } + file, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0640) + if err != nil { + return size, err + } + size, err = io.Copy(file, resp.Body) + if err != nil { + return size, err + } + if size != declSize { + err := fmt.Errorf("Mismatch Content-Length and recorded filesize") + return size, err + } + return size, err +} + +func (cli *Client) DeleteFile(ctx context.Context, ref, filename string) error { + var err error + ref, err = convertFileRefer(ref) + if err != nil { + return err + } + req, err := http.NewRequestWithContext(ctx, http.MethodDelete, ref, nil) + if err != nil { + return err + } + + if cli.username != "" && cli.password != "" { + req.Header.Add("Authorization", encodeBasicAuth(cli.username, cli.password)) + } + + transport := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + client := &http.Client{ + Transport: transport, + } + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + err := fmt.Errorf("Received wrong status code: %s", resp.Status) + return err + } + return err +}