diff --git a/internal/storage/postgres/company/company.go b/internal/storage/postgres/company/company.go
index 3b7e155..0e85f59 100644
--- a/internal/storage/postgres/company/company.go
+++ b/internal/storage/postgres/company/company.go
@@ -27,6 +27,7 @@ const (
addCompany = "INSERT INTO companies (token, owner_id, name, email) VALUES ($1, $2, $3, $4) RETURNING id, token, owner_id, name, email"
getCompanyByOwnerId = "SELECT c.id, c.token, c.owner_id, c.name, c.email FROM companies AS c INNER JOIN owners As o ON o.id = c.owner_id WHERE o.telegram_id=$1"
getCompanyIdByName = "SELECT id FROM companies WHERE name=$1"
+ updateToken = "UPDATE companies SET token=$1 WHERE id=$2"
)
// AddCompany adds a new company to the database and returns the newly created company.
@@ -61,3 +62,15 @@ func (s *CompanyStorage) GetCompanyByOwnerTelegramId(ctx context.Context, ownerI
return company, nil
}
+
+// UpdateToken updates the company's token.
+func (s *CompanyStorage) UpdateToken(ctx context.Context, companyId int64, token string) error {
+ const op = "storage.postgres.UpdateToken"
+
+ _, err := s.db.ExecContext(ctx, updateToken, token, companyId)
+ if err != nil {
+ return fmt.Errorf("%s: execute query: %w", op, err)
+ }
+
+ return nil
+}
diff --git a/internal/storage/postgres/company/company_test.go b/internal/storage/postgres/company/company_test.go
index 36ddfb7..07ffa6c 100644
--- a/internal/storage/postgres/company/company_test.go
+++ b/internal/storage/postgres/company/company_test.go
@@ -148,3 +148,72 @@ func TestCompanyStorage_GetCompanyByOwnerTelegramId(t *testing.T) {
assert.Equal(t, entities.Company{}, company)
})
}
+
+func TestCompanyStorage_UpdateToken(t *testing.T) {
+ t.Run("with company", func(t *testing.T) {
+ // Arrange
+ t.Parallel()
+ f := database.NewFixture(t)
+ defer f.Teardown()
+
+ var companyID int64 = 12
+ var token = "bguFFFTF&32r23r23t"
+
+ f.Mock.ExpectExec(regexp.QuoteMeta("UPDATE companies SET token=$1 WHERE id=$2")).
+ WithArgs(token, companyID).
+ WillReturnResult(sqlmock.NewResult(1, 1))
+
+ repo := New(f.DB)
+
+ // Act
+ err := repo.UpdateToken(context.Background(), companyID, token)
+
+ // Assert
+ assert.NoError(t, err)
+ })
+
+ t.Run("without company", func(t *testing.T) {
+ // Arrange
+ t.Parallel()
+ f := database.NewFixture(t)
+ defer f.Teardown()
+
+ var companyID int64 = 12
+ var token = "bguFFFTF&32r23r23t"
+
+ f.Mock.ExpectExec(regexp.QuoteMeta("UPDATE companies SET token=$1 WHERE id=$2")).
+ WithArgs(token, companyID).
+ WillReturnResult(sqlmock.NewResult(0, 0))
+
+ repo := New(f.DB)
+
+ // Act
+ err := repo.UpdateToken(context.Background(), companyID, token)
+
+ // Assert
+ assert.NoError(t, err)
+ })
+
+ t.Run("with error", func(t *testing.T) {
+ // Arrange
+ t.Parallel()
+ f := database.NewFixture(t)
+ defer f.Teardown()
+
+ expectErr := errors.New("test error")
+
+ var companyID int64 = 12
+ var token = "bguFFFTF&32r23r23t"
+
+ f.Mock.ExpectExec(regexp.QuoteMeta("UPDATE companies SET token=$1 WHERE id=$2")).
+ WithArgs(token, companyID).
+ WillReturnError(expectErr)
+ repo := New(f.DB)
+
+ // Act
+ err := repo.UpdateToken(context.Background(), companyID, token)
+
+ // Assert
+ assert.ErrorIs(t, err, expectErr)
+ })
+}
diff --git a/internal/transport/telegram/commands/company.go b/internal/transport/telegram/commands/company.go
index 4399b8b..6a021e8 100644
--- a/internal/transport/telegram/commands/company.go
+++ b/internal/transport/telegram/commands/company.go
@@ -12,6 +12,7 @@ import (
type companyUsesaces interface {
GetCompanyByOwnerTelegramId(ctx context.Context, ownerId int64) (entities.CompanyInfo, error)
+ UpdateToken(ctx context.Context, ownerId int64) error
}
// CompanyCommands represents a set of commands related to companies.
@@ -67,3 +68,30 @@ func (c *CompanyCommands) GetMyCompanies(m *tgbotapi.Message) (tgbotapi.MessageC
return msg, nil
}
+
+// UpdateToken updates the token of the company owned by the user who sent the message.
+// If the user does not own any companies, the message will indicate that they have no companies and provide a command to register a new one.
+// If an error occurs while retrieving the company information, an error message will be returned.
+func (c *CompanyCommands) UpdateToken(m *tgbotapi.Message) (tgbotapi.MessageConfig, error) {
+ const op = "CompanyCommands.UpdateToken"
+
+ msg := tgbotapi.NewMessage(m.Chat.ID, "")
+ msg.ParseMode = tgbotapi.ModeHTML
+
+ err := c.cu.UpdateToken(context.Background(), m.From.ID)
+ if err != nil {
+ if errors.Is(err, usecases.ErrCompanyNotFound) {
+ msg.Text = `
+ You have no companies
+
+ You can register new company with /register command
+ `
+ return msg, nil
+ }
+ msg.Text = "Something went wrong. Lets try again"
+ return msg, fmt.Errorf("%s: update token: %w", op, err)
+ }
+
+ msg.Text = "Token updated successfully"
+ return msg, nil
+}
diff --git a/internal/transport/telegram/commands/help.go b/internal/transport/telegram/commands/help.go
index 431c18e..d28055e 100644
--- a/internal/transport/telegram/commands/help.go
+++ b/internal/transport/telegram/commands/help.go
@@ -14,6 +14,7 @@ func GetHelpMessage(m *tgbotapi.Message) tgbotapi.MessageConfig {
/getchatid - show chat ID
/register - register new company
/getcompany - show registered company
+ /updatetoken - update company token
/addchat {chat_id} - add chat to company, for example: /addchat 123456789
/deletechat {chat_id} - delete chat from company, for example: /deletechat 123456789
`)
diff --git a/internal/transport/telegram/telegram.go b/internal/transport/telegram/telegram.go
index ecb5e98..1fb7330 100644
--- a/internal/transport/telegram/telegram.go
+++ b/internal/transport/telegram/telegram.go
@@ -13,13 +13,14 @@ import (
)
const (
- rigesterCommand = "register"
- getChatIdCommand = "getchatid"
- getCompanyCommand = "getcompany"
- addChatCommand = "addchat"
- helpCommand = "help"
- deleteChatCommand = "deletechat"
- startCommand = "start"
+ rigesterCommand = "register"
+ getChatIdCommand = "getchatid"
+ getCompanyCommand = "getcompany"
+ addChatCommand = "addchat"
+ helpCommand = "help"
+ deleteChatCommand = "deletechat"
+ startCommand = "start"
+ updateTokenCommand = "updatetoken"
)
type registrator interface {
@@ -29,6 +30,7 @@ type registrator interface {
type companyCommands interface {
GetMyCompanies(m *tgbotapi.Message) (tgbotapi.MessageConfig, error)
+ UpdateToken(m *tgbotapi.Message) (tgbotapi.MessageConfig, error)
}
type chatCommands interface {
@@ -124,6 +126,14 @@ func (b *TelegramBot) Run() {
}
b.sendMessage(msg)
continue
+
+ case updateTokenCommand:
+ msg, err := b.cc.UpdateToken(update.Message)
+ if err != nil {
+ b.logger.Error("cannot update token", sl.Err(err))
+ }
+ b.sendMessage(msg)
+ continue
case addChatCommand:
msg, err := b.chc.AddChat(update.Message)
if err != nil {
diff --git a/internal/usecases/company.go b/internal/usecases/company.go
index 8d10f88..7781dc5 100644
--- a/internal/usecases/company.go
+++ b/internal/usecases/company.go
@@ -6,12 +6,14 @@ import (
"fmt"
"github.com/testit-tms/webhook-bot/internal/entities"
+ "github.com/testit-tms/webhook-bot/internal/lib/random"
"github.com/testit-tms/webhook-bot/internal/storage"
)
//go:generate mockgen -source=$GOFILE -destination=$PWD/mocks/${GOFILE} -package=mocks
type companyStorage interface {
GetCompanyByOwnerTelegramId(ctx context.Context, ownerId int64) (entities.Company, error)
+ UpdateToken(ctx context.Context, companyId int64, token string) error
}
//go:generate mockgen -source=$GOFILE -destination=$PWD/mocks/${GOFILE} -package=mocks
@@ -72,3 +74,25 @@ func (u *companyUsecases) GetCompanyByOwnerTelegramId(ctx context.Context, owner
return ci, nil
}
+
+// UpdateToken updates the company token.
+// It returns an error if the company is not found.
+func (u *companyUsecases) UpdateToken(ctx context.Context, ownerId int64) error {
+ const op = "usecases.UpdateToken"
+
+ company, err := u.cs.GetCompanyByOwnerTelegramId(ctx, ownerId)
+ if err != nil {
+ if errors.Is(err, storage.ErrNotFound) {
+ return fmt.Errorf("%s: %w", op, ErrCompanyNotFound)
+ }
+ return fmt.Errorf("%s: get company by owner id: %w", op, err)
+ }
+
+ token := random.NewRandomString(30)
+
+ if err := u.cs.UpdateToken(ctx, company.ID, token); err != nil {
+ return fmt.Errorf("%s: update token: %w", op, err)
+ }
+
+ return nil
+}
\ No newline at end of file
diff --git a/internal/usecases/company_test.go b/internal/usecases/company_test.go
index 7b2eb04..8a6bf04 100644
--- a/internal/usecases/company_test.go
+++ b/internal/usecases/company_test.go
@@ -162,3 +162,84 @@ func Test_companyUsecases_GetCompanyByOwnerId(t *testing.T) {
})
}
}
+
+func Test_companyUsecases_UpdateToken(t *testing.T) {
+ tests := []struct {
+ name string
+ ownerId int64
+ wantErr bool
+ mockCompEntities entities.Company
+ mockCompError error
+ mockCompTimes int
+ mockUpdateTokenError error
+ mockUpdateTokenTimes int
+ }{
+ {
+ name: "success",
+ ownerId: 1,
+ wantErr: false,
+ mockCompEntities: entities.Company{
+ ID: 12,
+ OwnerID: 21,
+ Token: "token",
+ Name: "Yandex",
+ Email: "",
+ },
+ mockCompError: nil,
+ mockCompTimes: 1,
+ mockUpdateTokenError: nil,
+ mockUpdateTokenTimes: 1,
+ },
+ {
+ name: "company with ErrNotFound",
+ ownerId: 1,
+ wantErr: true,
+ mockCompEntities: entities.Company{},
+ mockCompError: storage.ErrNotFound,
+ mockCompTimes: 1,
+ },
+ {
+ name: "company with other error",
+ ownerId: 1,
+ wantErr: true,
+ mockCompEntities: entities.Company{},
+ mockCompError: errors.New("test error"),
+ mockCompTimes: 1,
+ },
+ {
+ name: "update token with error",
+ wantErr: true,
+ mockCompEntities: entities.Company{
+ ID: 12,
+ OwnerID: 21,
+ Token: "token",
+ Name: "Yandex",
+ Email: "",
+ },
+ mockCompError: nil,
+ mockCompTimes: 1,
+ mockUpdateTokenError: errors.New("test error"),
+ mockUpdateTokenTimes: 1,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ mockCtrl := gomock.NewController(t)
+ companyMock := mocks.NewMockcompanyStorage(mockCtrl)
+ companyMock.EXPECT().GetCompanyByOwnerTelegramId(gomock.Any(), tt.ownerId).Return(tt.mockCompEntities, tt.mockCompError).Times(tt.mockCompTimes)
+ if tt.mockCompError == nil {
+ companyMock.EXPECT().UpdateToken(gomock.Any(), tt.mockCompEntities.ID, gomock.Any()).Return(tt.mockUpdateTokenError).Times(tt.mockUpdateTokenTimes)
+ }
+
+ u := NewCompanyUsecases(companyMock, nil)
+
+ err := u.UpdateToken(context.Background(), tt.ownerId)
+ if err != nil {
+ if !tt.wantErr {
+ t.Errorf("companyUsecases.UpdateToken() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ }
+ })
+ }
+}
diff --git a/internal/usecases/mocks/company.go b/internal/usecases/mocks/company.go
index 77eadde..9557dca 100644
--- a/internal/usecases/mocks/company.go
+++ b/internal/usecases/mocks/company.go
@@ -50,6 +50,20 @@ func (mr *MockcompanyStorageMockRecorder) GetCompanyByOwnerTelegramId(ctx, owner
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCompanyByOwnerTelegramId", reflect.TypeOf((*MockcompanyStorage)(nil).GetCompanyByOwnerTelegramId), ctx, ownerId)
}
+// UpdateToken mocks base method.
+func (m *MockcompanyStorage) UpdateToken(ctx context.Context, companyId int64, token string) error {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "UpdateToken", ctx, companyId, token)
+ ret0, _ := ret[0].(error)
+ return ret0
+}
+
+// UpdateToken indicates an expected call of UpdateToken.
+func (mr *MockcompanyStorageMockRecorder) UpdateToken(ctx, companyId, token interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateToken", reflect.TypeOf((*MockcompanyStorage)(nil).UpdateToken), ctx, companyId, token)
+}
+
// MockchatStorage is a mock of chatStorage interface.
type MockchatStorage struct {
ctrl *gomock.Controller
diff --git a/makefile b/makefile
index 2e25679..13fd53f 100644
--- a/makefile
+++ b/makefile
@@ -36,6 +36,7 @@ test:
coverage:
go test -v ./... -coverprofile=coverage.out
go tool cover -func ./coverage.out
+ go tool cover -html ./coverage.out
.PHONY: lint
lint: