課題作成時にもTelegram/LINEに通知する機能を追加
This commit is contained in:
@@ -14,12 +14,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type AssignmentHandler struct {
|
type AssignmentHandler struct {
|
||||||
assignmentService *service.AssignmentService
|
assignmentService *service.AssignmentService
|
||||||
|
notificationService *service.NotificationService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAssignmentHandler() *AssignmentHandler {
|
func NewAssignmentHandler(notificationService *service.NotificationService) *AssignmentHandler {
|
||||||
return &AssignmentHandler{
|
return &AssignmentHandler{
|
||||||
assignmentService: service.NewAssignmentService(),
|
assignmentService: service.NewAssignmentService(),
|
||||||
|
notificationService: notificationService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +231,7 @@ func (h *AssignmentHandler) Create(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_, err = h.assignmentService.Create(userID, title, description, subject, priority, dueDate, reminderEnabled, reminderAt, urgentReminderEnabled)
|
assignment, err := h.assignmentService.Create(userID, title, description, subject, priority, dueDate, reminderEnabled, reminderAt, urgentReminderEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
role, _ := c.Get(middleware.UserRoleKey)
|
role, _ := c.Get(middleware.UserRoleKey)
|
||||||
name, _ := c.Get(middleware.UserNameKey)
|
name, _ := c.Get(middleware.UserNameKey)
|
||||||
@@ -245,6 +247,10 @@ func (h *AssignmentHandler) Create(c *gin.Context) {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if h.notificationService != nil {
|
||||||
|
go h.notificationService.SendAssignmentCreatedNotification(userID, assignment)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Redirect(http.StatusFound, "/assignments")
|
c.Redirect(http.StatusFound, "/assignments")
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ func (h *ProfileHandler) UpdateNotificationSettings(c *gin.Context) {
|
|||||||
TelegramChatID: c.PostForm("telegram_chat_id"),
|
TelegramChatID: c.PostForm("telegram_chat_id"),
|
||||||
LineEnabled: c.PostForm("line_enabled") == "on",
|
LineEnabled: c.PostForm("line_enabled") == "on",
|
||||||
LineNotifyToken: c.PostForm("line_token"),
|
LineNotifyToken: c.PostForm("line_token"),
|
||||||
|
NotifyOnCreate: c.PostForm("notify_on_create") == "on",
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.notificationService.UpdateUserSettings(userID, settings)
|
err := h.notificationService.UpdateUserSettings(userID, settings)
|
||||||
@@ -172,4 +173,3 @@ func (h *ProfileHandler) UpdateNotificationSettings(c *gin.Context) {
|
|||||||
"notifySettings": notifySettings,
|
"notifySettings": notifySettings,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ type UserNotificationSettings struct {
|
|||||||
TelegramChatID string `json:"telegram_chat_id"`
|
TelegramChatID string `json:"telegram_chat_id"`
|
||||||
LineEnabled bool `gorm:"default:false" json:"line_enabled"`
|
LineEnabled bool `gorm:"default:false" json:"line_enabled"`
|
||||||
LineNotifyToken string `json:"-"`
|
LineNotifyToken string `json:"-"`
|
||||||
|
NotifyOnCreate bool `gorm:"default:true" json:"notify_on_create"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ func Setup(cfg *config.Config) *gin.Engine {
|
|||||||
notificationService.StartReminderScheduler()
|
notificationService.StartReminderScheduler()
|
||||||
|
|
||||||
authHandler := handler.NewAuthHandler()
|
authHandler := handler.NewAuthHandler()
|
||||||
assignmentHandler := handler.NewAssignmentHandler()
|
assignmentHandler := handler.NewAssignmentHandler(notificationService)
|
||||||
adminHandler := handler.NewAdminHandler()
|
adminHandler := handler.NewAdminHandler()
|
||||||
profileHandler := handler.NewProfileHandler(notificationService)
|
profileHandler := handler.NewProfileHandler(notificationService)
|
||||||
apiHandler := handler.NewAPIHandler()
|
apiHandler := handler.NewAPIHandler()
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ func NewNotificationService(telegramBotToken string) *NotificationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func (s *NotificationService) GetUserSettings(userID uint) (*models.UserNotificationSettings, error) {
|
func (s *NotificationService) GetUserSettings(userID uint) (*models.UserNotificationSettings, error) {
|
||||||
var settings models.UserNotificationSettings
|
var settings models.UserNotificationSettings
|
||||||
result := database.GetDB().Where("user_id = ?", userID).First(&settings)
|
result := database.GetDB().Where("user_id = ?", userID).First(&settings)
|
||||||
@@ -41,14 +40,14 @@ func (s *NotificationService) GetUserSettings(userID uint) (*models.UserNotifica
|
|||||||
|
|
||||||
func (s *NotificationService) UpdateUserSettings(userID uint, settings *models.UserNotificationSettings) error {
|
func (s *NotificationService) UpdateUserSettings(userID uint, settings *models.UserNotificationSettings) error {
|
||||||
settings.UserID = userID
|
settings.UserID = userID
|
||||||
|
|
||||||
var existing models.UserNotificationSettings
|
var existing models.UserNotificationSettings
|
||||||
result := database.GetDB().Where("user_id = ?", userID).First(&existing)
|
result := database.GetDB().Where("user_id = ?", userID).First(&existing)
|
||||||
|
|
||||||
if result.RowsAffected == 0 {
|
if result.RowsAffected == 0 {
|
||||||
return database.GetDB().Create(settings).Error
|
return database.GetDB().Create(settings).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.ID = existing.ID
|
settings.ID = existing.ID
|
||||||
return database.GetDB().Save(settings).Error
|
return database.GetDB().Save(settings).Error
|
||||||
}
|
}
|
||||||
@@ -61,28 +60,28 @@ func (s *NotificationService) SendTelegramNotification(chatID, message string) e
|
|||||||
}
|
}
|
||||||
|
|
||||||
apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", s.telegramBotToken)
|
apiURL := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", s.telegramBotToken)
|
||||||
|
|
||||||
payload := map[string]string{
|
payload := map[string]string{
|
||||||
"chat_id": chatID,
|
"chat_id": chatID,
|
||||||
"text": message,
|
"text": message,
|
||||||
"parse_mode": "HTML",
|
"parse_mode": "HTML",
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonPayload, err := json.Marshal(payload)
|
jsonPayload, err := json.Marshal(payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := http.Post(apiURL, "application/json", bytes.NewBuffer(jsonPayload))
|
resp, err := http.Post(apiURL, "application/json", bytes.NewBuffer(jsonPayload))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return fmt.Errorf("telegram API returned status %d", resp.StatusCode)
|
return fmt.Errorf("telegram API returned status %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,29 +91,29 @@ func (s *NotificationService) SendLineNotification(token, message string) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
apiURL := "https://notify-api.line.me/api/notify"
|
apiURL := "https://notify-api.line.me/api/notify"
|
||||||
|
|
||||||
data := url.Values{}
|
data := url.Values{}
|
||||||
data.Set("message", message)
|
data.Set("message", message)
|
||||||
|
|
||||||
req, err := http.NewRequest("POST", apiURL, strings.NewReader(data.Encode()))
|
req, err := http.NewRequest("POST", apiURL, strings.NewReader(data.Encode()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
client := &http.Client{Timeout: 10 * time.Second}
|
client := &http.Client{Timeout: 10 * time.Second}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return fmt.Errorf("LINE Notify API returned status %d", resp.StatusCode)
|
return fmt.Errorf("LINE Notify API returned status %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +152,63 @@ func (s *NotificationService) SendAssignmentReminder(userID uint, assignment *mo
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *NotificationService) SendAssignmentCreatedNotification(userID uint, assignment *models.Assignment) error {
|
||||||
|
settings, err := s.GetUserSettings(userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.NotifyOnCreate {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !settings.TelegramEnabled && !settings.LineEnabled {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
message := fmt.Sprintf(
|
||||||
|
"新しい課題が追加されました\n\n【%s】\n科目: %s\n優先度: %s\n期限: %s\n\n%s",
|
||||||
|
assignment.Title,
|
||||||
|
assignment.Subject,
|
||||||
|
getPriorityLabel(assignment.Priority),
|
||||||
|
assignment.DueDate.Format("2006/01/02 15:04"),
|
||||||
|
assignment.Description,
|
||||||
|
)
|
||||||
|
|
||||||
|
var errors []string
|
||||||
|
|
||||||
|
if settings.TelegramEnabled && settings.TelegramChatID != "" {
|
||||||
|
if err := s.SendTelegramNotification(settings.TelegramChatID, message); err != nil {
|
||||||
|
errors = append(errors, fmt.Sprintf("Telegram: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if settings.LineEnabled && settings.LineNotifyToken != "" {
|
||||||
|
if err := s.SendLineNotification(settings.LineNotifyToken, message); err != nil {
|
||||||
|
errors = append(errors, fmt.Sprintf("LINE: %v", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return fmt.Errorf("notification errors: %s", strings.Join(errors, "; "))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPriorityLabel(priority string) string {
|
||||||
|
switch priority {
|
||||||
|
case "high":
|
||||||
|
return "大"
|
||||||
|
case "medium":
|
||||||
|
return "中"
|
||||||
|
case "low":
|
||||||
|
return "小"
|
||||||
|
default:
|
||||||
|
return priority
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *NotificationService) SendUrgentReminder(userID uint, assignment *models.Assignment) error {
|
func (s *NotificationService) SendUrgentReminder(userID uint, assignment *models.Assignment) error {
|
||||||
settings, err := s.GetUserSettings(userID)
|
settings, err := s.GetUserSettings(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -224,24 +280,24 @@ func getUrgentReminderInterval(priority string) time.Duration {
|
|||||||
|
|
||||||
func (s *NotificationService) ProcessPendingReminders() {
|
func (s *NotificationService) ProcessPendingReminders() {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
var assignments []models.Assignment
|
var assignments []models.Assignment
|
||||||
result := database.GetDB().Where(
|
result := database.GetDB().Where(
|
||||||
"reminder_enabled = ? AND reminder_sent = ? AND reminder_at <= ? AND is_completed = ?",
|
"reminder_enabled = ? AND reminder_sent = ? AND reminder_at <= ? AND is_completed = ?",
|
||||||
true, false, now, false,
|
true, false, now, false,
|
||||||
).Find(&assignments)
|
).Find(&assignments)
|
||||||
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
log.Printf("Error fetching pending reminders: %v", result.Error)
|
log.Printf("Error fetching pending reminders: %v", result.Error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, assignment := range assignments {
|
for _, assignment := range assignments {
|
||||||
if err := s.SendAssignmentReminder(assignment.UserID, &assignment); err != nil {
|
if err := s.SendAssignmentReminder(assignment.UserID, &assignment); err != nil {
|
||||||
log.Printf("Error sending reminder for assignment %d: %v", assignment.ID, err)
|
log.Printf("Error sending reminder for assignment %d: %v", assignment.ID, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
database.GetDB().Model(&assignment).Update("reminder_sent", true)
|
database.GetDB().Model(&assignment).Update("reminder_sent", true)
|
||||||
log.Printf("Sent reminder for assignment %d to user %d", assignment.ID, assignment.UserID)
|
log.Printf("Sent reminder for assignment %d to user %d", assignment.ID, assignment.UserID)
|
||||||
}
|
}
|
||||||
@@ -250,41 +306,41 @@ func (s *NotificationService) ProcessPendingReminders() {
|
|||||||
func (s *NotificationService) ProcessUrgentReminders() {
|
func (s *NotificationService) ProcessUrgentReminders() {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
urgentStartTime := 3 * time.Hour
|
urgentStartTime := 3 * time.Hour
|
||||||
|
|
||||||
var assignments []models.Assignment
|
var assignments []models.Assignment
|
||||||
result := database.GetDB().Where(
|
result := database.GetDB().Where(
|
||||||
"urgent_reminder_enabled = ? AND is_completed = ? AND due_date > ?",
|
"urgent_reminder_enabled = ? AND is_completed = ? AND due_date > ?",
|
||||||
true, false, now,
|
true, false, now,
|
||||||
).Find(&assignments)
|
).Find(&assignments)
|
||||||
|
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
log.Printf("Error fetching urgent reminders: %v", result.Error)
|
log.Printf("Error fetching urgent reminders: %v", result.Error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, assignment := range assignments {
|
for _, assignment := range assignments {
|
||||||
timeUntilDue := assignment.DueDate.Sub(now)
|
timeUntilDue := assignment.DueDate.Sub(now)
|
||||||
|
|
||||||
if timeUntilDue > urgentStartTime {
|
if timeUntilDue > urgentStartTime {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
interval := getUrgentReminderInterval(assignment.Priority)
|
interval := getUrgentReminderInterval(assignment.Priority)
|
||||||
|
|
||||||
if assignment.LastUrgentReminderSent != nil {
|
if assignment.LastUrgentReminderSent != nil {
|
||||||
timeSinceLastReminder := now.Sub(*assignment.LastUrgentReminderSent)
|
timeSinceLastReminder := now.Sub(*assignment.LastUrgentReminderSent)
|
||||||
if timeSinceLastReminder < interval {
|
if timeSinceLastReminder < interval {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.SendUrgentReminder(assignment.UserID, &assignment); err != nil {
|
if err := s.SendUrgentReminder(assignment.UserID, &assignment); err != nil {
|
||||||
log.Printf("Error sending urgent reminder for assignment %d: %v", assignment.ID, err)
|
log.Printf("Error sending urgent reminder for assignment %d: %v", assignment.ID, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
database.GetDB().Model(&assignment).Update("last_urgent_reminder_sent", now)
|
database.GetDB().Model(&assignment).Update("last_urgent_reminder_sent", now)
|
||||||
log.Printf("Sent urgent reminder for assignment %d (priority: %s) to user %d",
|
log.Printf("Sent urgent reminder for assignment %d (priority: %s) to user %d",
|
||||||
assignment.ID, assignment.Priority, assignment.UserID)
|
assignment.ID, assignment.Priority, assignment.UserID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -293,7 +349,7 @@ func (s *NotificationService) StartReminderScheduler() {
|
|||||||
go func() {
|
go func() {
|
||||||
ticker := time.NewTicker(1 * time.Minute)
|
ticker := time.NewTicker(1 * time.Minute)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for range ticker.C {
|
for range ticker.C {
|
||||||
s.ProcessPendingReminders()
|
s.ProcessPendingReminders()
|
||||||
s.ProcessUrgentReminders()
|
s.ProcessUrgentReminders()
|
||||||
@@ -301,4 +357,3 @@ func (s *NotificationService) StartReminderScheduler() {
|
|||||||
}()
|
}()
|
||||||
log.Println("Reminder scheduler started (one-time + urgent reminders)")
|
log.Println("Reminder scheduler started (one-time + urgent reminders)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr class="my-3">
|
||||||
|
<div class="form-check form-switch mb-3">
|
||||||
|
<input class="form-check-input" type="checkbox" id="notify_on_create" name="notify_on_create"
|
||||||
|
{{if .notifySettings.NotifyOnCreate}}checked{{end}}>
|
||||||
|
<label class="form-check-label" for="notify_on_create">
|
||||||
|
<i class="bi bi-plus-circle me-1"></i>課題追加時に通知する
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
<button type="submit" class="btn btn-primary"><i class="bi bi-check-lg me-1"></i>通知設定を保存</button>
|
<button type="submit" class="btn btn-primary"><i class="bi bi-check-lg me-1"></i>通知設定を保存</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user