Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 0 additions & 11 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"os"

"github.com/spf13/cobra"
"github.com/systemli/ticker/internal/bridge"
"github.com/systemli/ticker/internal/config"
"github.com/systemli/ticker/internal/logger"
"github.com/systemli/ticker/internal/storage"
Expand Down Expand Up @@ -38,16 +37,6 @@ func initConfig() {
// Initialize the global logger with configuration
loggerInstance := logger.Initialize(cfg.LogLevel, cfg.LogFormat)

//TODO: Improve startup routine
if cfg.Telegram.Enabled() {
user, err := bridge.BotUser(cfg.Telegram.Token)
if err != nil {
log.WithError(err).Error("unable to retrieve the user information for the telegram bot")
} else {
cfg.Telegram.User = user
}
}

var err error
db, err = storage.OpenGormDB(cfg.Database.Type, cfg.Database.DSN, loggerInstance)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ func API(config config.Config, store storage.Storage) *Server {

admin.GET(`/settings/:name`, user.NeedAdmin(), handler.GetSetting)
admin.PUT(`/settings/inactive_settings`, user.NeedAdmin(), handler.PutInactiveSettings)
admin.PUT(`/settings/telegram_settings`, user.NeedAdmin(), handler.PutTelegramSettings)
}

public := r.Group("/v1").Use()
Expand Down
8 changes: 5 additions & 3 deletions internal/api/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,20 @@ import (
"github.com/gin-gonic/gin"
"github.com/systemli/ticker/internal/api/response"
"github.com/systemli/ticker/internal/config"
"github.com/systemli/ticker/internal/storage"
)

type FeaturesResponse map[string]bool

func NewFeaturesResponse(config config.Config) FeaturesResponse {
func NewFeaturesResponse(config config.Config, storage storage.Storage) FeaturesResponse {
telegramSettings := storage.GetTelegramSettings()
return FeaturesResponse{
"telegramEnabled": config.Telegram.Enabled(),
"telegramEnabled": telegramSettings.Token != "",
"signalGroupEnabled": config.SignalGroup.Enabled(),
}
}

func (h *handler) GetFeatures(c *gin.Context) {
features := NewFeaturesResponse(h.config)
features := NewFeaturesResponse(h.config, h.storage)
c.JSON(http.StatusOK, response.SuccessResponse(map[string]interface{}{"features": features}))
}
3 changes: 3 additions & 0 deletions internal/api/features_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ func (s *FeaturesTestSuite) TestGetFeatures() {
c, _ := gin.CreateTestContext(w)
store := &storage.MockStorage{}

// Mock GetTelegramSettings to return empty token (disabled)
store.On("GetTelegramSettings").Return(storage.TelegramSettings{Token: ""})

h := handler{
storage: store,
config: config.LoadConfig(""),
Expand Down
7 changes: 7 additions & 0 deletions internal/api/response/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,10 @@ func InactiveSettingsResponse(inactiveSettings storage.InactiveSettings) Setting
Value: inactiveSettings,
}
}

func TelegramSettingsResponse(telegramSettings storage.TelegramSettings) Setting {
return Setting{
Name: storage.SettingTelegramName,
Value: telegramSettings,
}
}
48 changes: 48 additions & 0 deletions internal/api/response/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,54 @@ func (s *SettingsResponseTestSuite) TestInactiveSettingsResponse() {
s.Equal(inactiveSettings, setting.Value)
}

func (s *SettingsResponseTestSuite) TestTelegramSettingsResponse() {
s.Run("with default settings", func() {
telegramSettings := storage.DefaultTelegramSettings()

setting := TelegramSettingsResponse(telegramSettings)

s.Equal(storage.SettingTelegramName, setting.Name)
s.Equal(telegramSettings, setting.Value)
s.Equal("", setting.Value.(storage.TelegramSettings).Token)
})

s.Run("with token set", func() {
telegramSettings := storage.TelegramSettings{
Token: "123456789:ABCdefGHIjklMNOpqrsTUVwxyz",
}

setting := TelegramSettingsResponse(telegramSettings)

s.Equal(storage.SettingTelegramName, setting.Name)
s.Equal(telegramSettings, setting.Value)
s.Equal("123456789:ABCdefGHIjklMNOpqrsTUVwxyz", setting.Value.(storage.TelegramSettings).Token)
})

s.Run("with empty token", func() {
telegramSettings := storage.TelegramSettings{
Token: "",
}

setting := TelegramSettingsResponse(telegramSettings)

s.Equal(storage.SettingTelegramName, setting.Name)
s.Equal(telegramSettings, setting.Value)
s.Equal("", setting.Value.(storage.TelegramSettings).Token)
})

s.Run("response structure validation", func() {
telegramSettings := storage.TelegramSettings{
Token: "test-token",
}

setting := TelegramSettingsResponse(telegramSettings)

s.IsType(Setting{}, setting)
s.IsType(storage.TelegramSettings{}, setting.Value)
s.NotEmpty(setting.Name)
})
}

func TestSettingsResponseTestSuite(t *testing.T) {
suite.Run(t, new(SettingsResponseTestSuite))
}
9 changes: 4 additions & 5 deletions internal/api/response/ticker.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package response
import (
"time"

"github.com/systemli/ticker/internal/config"
"github.com/systemli/ticker/internal/storage"
)

Expand Down Expand Up @@ -76,7 +75,7 @@ type Location struct {
Lon float64 `json:"lon"`
}

func TickerResponse(t storage.Ticker, config config.Config) Ticker {
func TickerResponse(t storage.Ticker, botUsername string) Ticker {
websites := make([]Website, 0)
for _, website := range t.Websites {
websites = append(websites, Website{
Expand Down Expand Up @@ -108,7 +107,7 @@ func TickerResponse(t storage.Ticker, config config.Config) Ticker {
Telegram: Telegram{
Active: t.Telegram.Active,
Connected: t.Telegram.Connected(),
BotUsername: config.Telegram.User.UserName,
BotUsername: botUsername,
ChannelName: t.Telegram.ChannelName,
},
Mastodon: Mastodon{
Expand Down Expand Up @@ -137,11 +136,11 @@ func TickerResponse(t storage.Ticker, config config.Config) Ticker {
}
}

func TickersResponse(tickers []storage.Ticker, config config.Config) []Ticker {
func TickersResponse(tickers []storage.Ticker, botUsername string) []Ticker {
t := make([]Ticker, 0)

for _, ticker := range tickers {
t = append(t, TickerResponse(ticker, config))
t = append(t, TickerResponse(ticker, botUsername))
}
return t
}
14 changes: 2 additions & 12 deletions internal/api/response/ticker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import (
"testing"
"time"

tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/stretchr/testify/suite"
"github.com/systemli/ticker/internal/config"
"github.com/systemli/ticker/internal/storage"
)

Expand Down Expand Up @@ -61,15 +59,7 @@ func (s *TickersResponseTestSuite) TestTickersResponse() {
},
}

config := config.Config{
Telegram: config.Telegram{
User: tgbotapi.User{
UserName: "ticker",
},
},
}

tickerResponse := TickersResponse([]storage.Ticker{ticker}, config)
tickerResponse := TickersResponse([]storage.Ticker{ticker}, "")

s.Equal(1, len(tickerResponse))
s.Equal(ticker.ID, tickerResponse[0].ID)
Expand All @@ -89,7 +79,7 @@ func (s *TickersResponseTestSuite) TestTickersResponse() {
s.Equal(ticker.Websites[0].Origin, tickerResponse[0].Websites[0].Origin)
s.Equal(ticker.Telegram.Active, tickerResponse[0].Telegram.Active)
s.Equal(ticker.Telegram.Connected(), tickerResponse[0].Telegram.Connected)
s.Equal(config.Telegram.User.UserName, tickerResponse[0].Telegram.BotUsername)
s.Equal("", tickerResponse[0].Telegram.BotUsername) // Now empty since removed from config
s.Equal(ticker.Telegram.ChannelName, tickerResponse[0].Telegram.ChannelName)
s.Equal(ticker.Mastodon.Active, tickerResponse[0].Mastodon.Active)
s.Equal(ticker.Mastodon.Connected(), tickerResponse[0].Mastodon.Connected)
Expand Down
26 changes: 26 additions & 0 deletions internal/api/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ func (h *handler) GetSetting(c *gin.Context) {
return
}

if c.Param("name") == storage.SettingTelegramName {
setting := h.storage.GetTelegramSettings()
data := map[string]interface{}{"setting": response.TelegramSettingsResponse(setting)}
c.JSON(http.StatusOK, response.SuccessResponse(data))
return
}

c.JSON(http.StatusNotFound, response.ErrorResponse(response.CodeDefault, response.SettingNotFound))
}

Expand All @@ -43,3 +50,22 @@ func (h *handler) PutInactiveSettings(c *gin.Context) {
data := map[string]interface{}{"setting": response.InactiveSettingsResponse(setting)}
c.JSON(http.StatusOK, response.SuccessResponse(data))
}

func (h *handler) PutTelegramSettings(c *gin.Context) {
value := storage.TelegramSettings{}
err := c.Bind(&value)
if err != nil {
c.JSON(http.StatusBadRequest, response.ErrorResponse(response.CodeDefault, response.FormError))
return
}

err = h.storage.SaveTelegramSettings(value)
if err != nil {
c.JSON(http.StatusBadRequest, response.ErrorResponse(response.CodeDefault, response.StorageError))
return
}

setting := h.storage.GetTelegramSettings()
data := map[string]interface{}{"setting": response.TelegramSettingsResponse(setting)}
c.JSON(http.StatusOK, response.SuccessResponse(data))
}
95 changes: 95 additions & 0 deletions internal/api/settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,27 @@ func (s *SettingsTestSuite) TestGetSetting() {
s.Equal(http.StatusOK, s.w.Code)
s.store.AssertExpectations(s.T())
})

s.Run("get telegram settings", func() {
s.ctx.AddParam("name", storage.SettingTelegramName)
expectedSettings := storage.TelegramSettings{Token: "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"}
s.store.On("GetTelegramSettings").Return(expectedSettings).Once()
h := s.handler()
h.GetSetting(s.ctx)

s.Equal(http.StatusOK, s.w.Code)
s.Contains(s.w.Body.String(), "telegram_settings")
s.store.AssertExpectations(s.T())
})

s.Run("get unknown setting", func() {
s.ctx.AddParam("name", "unknown_setting")
h := s.handler()
h.GetSetting(s.ctx)

s.Equal(http.StatusNotFound, s.w.Code)
s.store.AssertExpectations(s.T())
})
}

func (s *SettingsTestSuite) TestPutInactiveSetting() {
Expand Down Expand Up @@ -98,6 +119,80 @@ func (s *SettingsTestSuite) TestPutInactiveSetting() {
})
}

func (s *SettingsTestSuite) TestPutTelegramSettings() {
s.Run("when body is invalid", func() {
// Even with invalid JSON, Gin creates empty struct and SaveTelegramSettings gets called
s.store.On("SaveTelegramSettings", storage.TelegramSettings{Token: ""}).Return(nil).Once()
s.store.On("GetTelegramSettings").Return(storage.TelegramSettings{Token: ""}).Once()
s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/settings/telegram_settings", strings.NewReader(`broken_json`))
h := s.handler()
h.PutTelegramSettings(s.ctx)

s.Equal(http.StatusOK, s.w.Code) // Changed to OK since Gin doesn't fail on malformed JSON
s.store.AssertExpectations(s.T())
})

s.Run("when storage returns error", func() {
setting := storage.TelegramSettings{Token: "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"}
body, _ := json.Marshal(setting)
s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/settings/telegram_settings", bytes.NewReader(body))
s.ctx.Request.Header.Add("Content-Type", "application/json")
s.store.On("SaveTelegramSettings", mock.Anything).Return(errors.New("storage error")).Once()
h := s.handler()
h.PutTelegramSettings(s.ctx)

s.Equal(http.StatusBadRequest, s.w.Code)
s.store.AssertExpectations(s.T())
})

s.Run("when storage saves settings successfully", func() {
setting := storage.TelegramSettings{Token: "123456789:ABCdefGHIjklMNOpqrsTUVwxyz"}
body, _ := json.Marshal(setting)
s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/settings/telegram_settings", bytes.NewReader(body))
s.ctx.Request.Header.Add("Content-Type", "application/json")
s.store.On("SaveTelegramSettings", mock.Anything).Return(nil).Once()
s.store.On("GetTelegramSettings").Return(setting)
h := s.handler()
h.PutTelegramSettings(s.ctx)

s.Equal(http.StatusOK, s.w.Code)
s.Contains(s.w.Body.String(), "telegram_settings")
s.Contains(s.w.Body.String(), "123456789:ABCdefGHIjklMNOpqrsTUVwxyz")
s.store.AssertExpectations(s.T())
})

s.Run("when saving empty token", func() {
setting := storage.TelegramSettings{Token: ""}
body, _ := json.Marshal(setting)
s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/settings/telegram_settings", bytes.NewReader(body))
s.ctx.Request.Header.Add("Content-Type", "application/json")
s.store.On("SaveTelegramSettings", mock.Anything).Return(nil).Once()
s.store.On("GetTelegramSettings").Return(setting)
h := s.handler()
h.PutTelegramSettings(s.ctx)

s.Equal(http.StatusOK, s.w.Code)
s.store.AssertExpectations(s.T())
})

s.Run("when saving very long token", func() {
// Test with a token that's longer than typical but still valid format
longToken := "123456789:ABCdefGHIjklMNOpqrsTUVwxyzABCdefGHIjklMNOpqrsTUVwxyzABCdefGHIjklMNOpqrsTUVwxyz"
setting := storage.TelegramSettings{Token: longToken}
body, _ := json.Marshal(setting)
s.ctx.Request = httptest.NewRequest(http.MethodPut, "/v1/admin/settings/telegram_settings", bytes.NewReader(body))
s.ctx.Request.Header.Add("Content-Type", "application/json")
s.store.On("SaveTelegramSettings", mock.Anything).Return(nil).Once()
s.store.On("GetTelegramSettings").Return(setting)
h := s.handler()
h.PutTelegramSettings(s.ctx)

s.Equal(http.StatusOK, s.w.Code)
s.Contains(s.w.Body.String(), longToken)
s.store.AssertExpectations(s.T())
})
}

func (s *SettingsTestSuite) handler() handler {
return handler{
storage: s.store,
Expand Down
Loading
Loading