Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scheduler refactoring #136

Merged
merged 11 commits into from
Dec 30, 2024
Merged
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
19 changes: 19 additions & 0 deletions internal/scheduler/admin_stats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package scheduler

import (
"context"

"github.com/capymind/internal/database"
"github.com/capymind/internal/helpers"
"github.com/capymind/internal/translator"
)

func prepareAdminStats(ctx *context.Context, locale translator.Locale, adminStorage database.AdminStorage, feedbackStorage database.FeedbackStorage) *string {
stats := helpers.GetStats(ctx, locale, adminStorage, feedbackStorage)

var finalString string
for _, stat := range stats {
finalString += stat + "\n"
}
return &finalString
}
22 changes: 22 additions & 0 deletions internal/scheduler/admin_stats_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package scheduler

import (
"context"
"testing"

"github.com/capymind/internal/mocks"
"github.com/capymind/internal/translator"
)

func TestAdminStats(t *testing.T) {
context := context.Background()
locale := translator.EN
adminStorage := mocks.AdminStorageMock{}
feedbackStorage := mocks.FeedbackStorageMock{}

response := prepareAdminStats(&context, locale, adminStorage, feedbackStorage)

if *response != "The total number of users is 100\nThe total number of active users is 75\nThe total number of notes is 999\n\nFeedback from last week 📈\n\nJohn \nDoe\n:\n\nTest feedback\n\nJohn \nDoe\n:\n\nTest feedback 2\n\n" {
t.Errorf("Expected valid response, got %s", *response)
}
}
113 changes: 113 additions & 0 deletions internal/scheduler/messaging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package scheduler

import (
"context"
"encoding/json"
"log"
"net/http"
"time"

"github.com/capymind/internal/botservice"
"github.com/capymind/internal/database"
"github.com/capymind/internal/taskservice"
"github.com/capymind/internal/translator"
)

func prepareMessage(user *database.User, ctx *context.Context, offset int, messageType taskservice.MessageType, message string, isCloud bool) {
//coverage:ignore
defer wg.Done()

log.Printf("[Scheduler] Schedule a message for user: %s", user.ID)

userLocale := translator.Locale(*user.Locale)

var localizedMessage *string
if messageType == taskservice.WeeklyAnalysis {
localizedMessage = prepareWeeklyAnalysis(user, ctx, userLocale, noteStorage, aiService)
} else if messageType == taskservice.UserStats {
// Send only to active users
if !user.IsActive() {
return
}
localizedMessage = prepareUserStats(user, ctx, userLocale, noteStorage)
} else if messageType == taskservice.AdminStats {
// Send only to admins
if !database.IsAdmin(user.Role) {
return
}
localizedMessage = prepareAdminStats(ctx, userLocale, adminStorage, feedbackStorage)
} else {
msg := translator.Translate(userLocale, message)
localizedMessage = &msg
}

if localizedMessage == nil {
return
}

var scheduledTime time.Time
if isCloud {
scheduledTime = time.Now().Add(time.Duration(offset) * time.Hour)
scheduledTime = scheduledTime.Add(-time.Duration(*user.SecondsFromUTC) * time.Second)
} else {
// For local testing, schedule the message in 10 seconds
scheduledTime = time.Now().Add(10 * time.Second)
}

scheduledMessage := taskservice.ScheduledTask{
ChatID: user.ChatID,
Text: *localizedMessage,
Type: messageType,
Locale: userLocale,
}

tasks.Schedule(ctx, scheduledMessage, scheduledTime)
}

// Send a message to a user
func SendMessage(w http.ResponseWriter, r *http.Request) {
//coverage:ignore
var msg taskservice.ScheduledTask
if err := json.NewDecoder(r.Body).Decode(&msg); err != nil {
log.Printf("[Scheduler] Could not parse message %s", err.Error())
return
}

result := prepareBotResult(msg)
bot.SendResult(msg.ChatID, result)
log.Printf("[Scheduler] Message sent to user: %d", msg.ChatID)
}

func prepareBotResult(scheduledTask taskservice.ScheduledTask) botservice.BotResult {
var result botservice.BotResult
switch scheduledTask.Type {
case taskservice.Morning, taskservice.Evening:
var button botservice.BotResultTextButton = botservice.BotResultTextButton{
TextID: "make_record_to_journal",
Locale: scheduledTask.Locale,
Callback: "/note",
}
result = botservice.BotResult{
TextID: scheduledTask.Text,
Locale: scheduledTask.Locale,
Buttons: []botservice.BotResultTextButton{button},
}
case taskservice.Feedback:
var button botservice.BotResultTextButton = botservice.BotResultTextButton{
TextID: "feedback_button",
Locale: scheduledTask.Locale,
Callback: "/support",
}
result = botservice.BotResult{
TextID: scheduledTask.Text,
Locale: scheduledTask.Locale,
Buttons: []botservice.BotResultTextButton{button},
}
default:
result = botservice.BotResult{
TextID: scheduledTask.Text,
Locale: scheduledTask.Locale,
}
}
return result
}
79 changes: 79 additions & 0 deletions internal/scheduler/messaging_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package scheduler

import (
"testing"

"github.com/capymind/internal/taskservice"
)

func TestPrepareMorningMessage(t *testing.T) {
scheduledMessage := taskservice.ScheduledTask{
ChatID: 1,
Text: "Bot result",
Type: taskservice.Morning,
Locale: "en",
}

result := prepareBotResult(scheduledMessage)
if result.TextID != "Bot result" {
t.Error("Expected Bot result, got nil")
}
if result.Locale != "en" {
t.Error("Expected en, got nil")
}
if result.Buttons[0].TextID != "make_record_to_journal" {
t.Error("Expected make_record_to_journal, got nil")
}
if result.Buttons[0].Locale != "en" {
t.Error("Expected en, got nil")
}
if result.Buttons[0].Callback != "/note" {
t.Error("Expected /note, got nil")
}
}

func TestPrepareFeedbackMessage(t *testing.T) {
scheduledMessage := taskservice.ScheduledTask{
ChatID: 1,
Text: "Bot result",
Type: taskservice.Feedback,
Locale: "en",
}

result := prepareBotResult(scheduledMessage)
if result.TextID != "Bot result" {
t.Error("Expected Bot result, got nil")
}
if result.Locale != "en" {
t.Error("Expected en, got nil")
}
if result.Buttons[0].TextID != "feedback_button" {
t.Error("Expected feedback_button, got nil")
}
if result.Buttons[0].Locale != "en" {
t.Error("Expected en, got nil")
}
if result.Buttons[0].Callback != "/support" {
t.Error("Expected /support, got nil")
}
}

func TestPrepareRegularMessage(t *testing.T) {
scheduledMessage := taskservice.ScheduledTask{
ChatID: 1,
Text: "Bot result",
Type: taskservice.Regular,
Locale: "en",
}

result := prepareBotResult(scheduledMessage)
if result.TextID != "Bot result" {
t.Error("Expected Bot result, got nil")
}
if result.Locale != "en" {
t.Error("Expected en, got nil")
}
if len(result.Buttons) != 0 {
t.Error("Expected no buttons, got some")
}
}
41 changes: 41 additions & 0 deletions internal/scheduler/parsing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package scheduler

import (
"fmt"
"log"
"net/url"
"time"

"github.com/capymind/internal/taskservice"
)

// Returns the type and offset parameters from the URL
func parse(url *url.URL) (*string, int) {
typeStr := url.Query().Get("type")
offsetStr := url.Query().Get("offset") // hours (from UTC 0)
var offset int = 0
if offsetStr != "" {
_, err := fmt.Sscanf(offsetStr, "%d", &offset)
if err != nil {
log.Printf("[Scheduler] Error getting offset parameter, %s", err.Error())
}
}
return &typeStr, offset
}

func getTextMessage(messageType taskservice.MessageType) *string {
var message string
switch messageType {
case taskservice.Morning, taskservice.Evening:
message = taskservice.GetMessage(messageType, time.Now().Weekday())
case taskservice.Feedback:
message = "ask_write_review_about_bot"
case taskservice.WeeklyAnalysis, taskservice.UserStats, taskservice.AdminStats:
// Personalized for each user
message = ""
default:
log.Println("Missing message type parameter")
return nil
}
return &message
}
107 changes: 107 additions & 0 deletions internal/scheduler/parsing_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package scheduler

import (
"net/url"
"testing"

"github.com/capymind/internal/taskservice"
)

func TestParse(t *testing.T) {
url1 := url.URL{
RawQuery: "type=morning&offset=2",
}

typeStr, offset := parse(&url1)
if *typeStr != "morning" {
t.Errorf("Expected type=morning, got %s", *typeStr)
}
if offset != 2 {
t.Errorf("Expected offset=2, got %d", offset)
}

url2 := url.URL{
RawQuery: "type=evening&offset=0",
}

typeStr, offset = parse(&url2)
if *typeStr != "evening" {
t.Errorf("Expected type=evening, got %s", *typeStr)
}
if offset != 0 {
t.Errorf("Expected offset=0, got %d", offset)
}

url3 := url.URL{
RawQuery: "type=weekly_analysis&offset=-1",
}

typeStr, offset = parse(&url3)
if *typeStr != "weekly_analysis" {
t.Errorf("Expected type=weekly_analysis, got %s", *typeStr)
}
if offset != -1 {
t.Errorf("Expected offset=-1, got %d", offset)
}

url4 := url.URL{
RawQuery: "type=aaa&",
}

typeStr, offset = parse(&url4)
if *typeStr != "aaa" {
t.Errorf("Expected nil, got %s", *typeStr)
}
if offset != 0 {
t.Errorf("Expected offset=0, got %d", offset)
}

url5 := url.URL{
RawQuery: "offset=2",
}

typeStr, offset = parse(&url5)
if *typeStr != "" {
t.Errorf("Expected nil, got %s", *typeStr)
}
if offset != 2 {
t.Errorf("Expected offset=2, got %d", offset)
}
}

func TestGetTextMessage(t *testing.T) {
message := getTextMessage(taskservice.Morning)
if *message != "how_are_you_morning_monday" {
t.Errorf("Expected how_are_you_morning_monday, got %s", *message)
}

message = getTextMessage(taskservice.Evening)
if *message != "how_are_you_evening_monday" {
t.Errorf("Expected how_are_you_evening_monday, got %s", *message)
}

message = getTextMessage(taskservice.Feedback)
if *message != "ask_write_review_about_bot" {
t.Errorf("Expected ask_write_review_about_bot, got %s", *message)
}

message = getTextMessage(taskservice.WeeklyAnalysis)
if *message != "" {
t.Errorf("Expected empty string, got %s", *message)
}

message = getTextMessage(taskservice.UserStats)
if *message != "" {
t.Errorf("Expected empty string, got %s", *message)
}

message = getTextMessage(taskservice.AdminStats)
if *message != "" {
t.Errorf("Expected empty string, got %s", *message)
}

message = getTextMessage("")
if message != nil {
t.Errorf("Expected nil, got %s", *message)
}
}
Loading
Loading