599 lines
14 KiB
Go
599 lines
14 KiB
Go
package controllers
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"server/middleware"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"server/models"
|
|
"server/pkg/jwtutil"
|
|
|
|
"github.com/beego/beego/v2/client/orm"
|
|
beego "github.com/beego/beego/v2/server/web"
|
|
)
|
|
|
|
type AppReminderController struct {
|
|
beego.Controller
|
|
}
|
|
|
|
func (c *AppReminderController) appClaims() (*jwtutil.Claims, error) {
|
|
auth := c.Ctx.Request.Header.Get("Authorization")
|
|
if auth == "" {
|
|
return nil, orm.ErrNoRows
|
|
}
|
|
parts := strings.SplitN(auth, " ", 2)
|
|
if len(parts) != 2 || parts[0] != "Bearer" {
|
|
return nil, orm.ErrNoRows
|
|
}
|
|
claims, err := jwtutil.ParseToken(parts[1])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if claims.UserType != "backend" && claims.UserType != "app" && claims.UserType != "platform" {
|
|
return nil, orm.ErrNoRows
|
|
}
|
|
return claims, nil
|
|
}
|
|
|
|
func (c *AppReminderController) jsonErr(httpStatus, bizCode int, msg string) {
|
|
c.Ctx.Output.SetStatus(httpStatus)
|
|
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
|
|
_ = c.ServeJSON()
|
|
}
|
|
|
|
func (c *AppReminderController) ok(data interface{}) {
|
|
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data}
|
|
_ = c.ServeJSON()
|
|
}
|
|
|
|
func appGenerateToken() string {
|
|
b := make([]byte, 16)
|
|
_, _ = rand.Read(b)
|
|
b[6] = (b[6] & 0x0f) | 0x40
|
|
b[8] = (b[8] & 0x3f) | 0x80
|
|
return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
|
|
}
|
|
|
|
type appSchedulePayload struct {
|
|
Content string `json:"content"`
|
|
ScheduleTime string `json:"schedule_time"`
|
|
RemindChannels []string `json:"remind_channels"`
|
|
AdvanceMinutes int `json:"advance_minutes"`
|
|
RepeatIntervalMinutes int `json:"repeat_interval_minutes"`
|
|
MaxSendCount int `json:"max_send_count"`
|
|
}
|
|
|
|
// GetList GET /app/schedule/list
|
|
func (c *AppReminderController) GetList() {
|
|
claims, err := c.appClaims()
|
|
if err != nil {
|
|
c.jsonErr(401, 401, "未登录或无权限")
|
|
return
|
|
}
|
|
|
|
page, _ := c.GetInt("page", 1)
|
|
pageSize, _ := c.GetInt("pageSize", 20)
|
|
keyword := c.GetString("keyword", "")
|
|
status := c.GetString("status", "")
|
|
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 || pageSize > 100 {
|
|
pageSize = 20
|
|
}
|
|
|
|
qs := models.Orm.QueryTable(new(models.BackendSchedule)).
|
|
Filter("user_id", claims.UserID).
|
|
Filter("tid", claims.TenantId)
|
|
|
|
if keyword != "" {
|
|
qs = qs.Filter("content__contains", keyword)
|
|
}
|
|
|
|
total, _ := qs.Count()
|
|
|
|
var schedules []models.BackendSchedule
|
|
_, err = qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&schedules)
|
|
if err != nil && err != orm.ErrNoRows {
|
|
c.jsonErr(500, 500, "查询失败: "+err.Error())
|
|
return
|
|
}
|
|
if schedules == nil {
|
|
schedules = []models.BackendSchedule{}
|
|
}
|
|
|
|
list := make([]map[string]interface{}, 0, len(schedules))
|
|
for _, s := range schedules {
|
|
var reminders []models.BackendScheduleReminder
|
|
_, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
|
|
Filter("schedule_id", s.ID).
|
|
Filter("is_deleted", 0).
|
|
All(&reminders)
|
|
|
|
channels := make([]string, 0, len(reminders))
|
|
isFinished := true
|
|
if len(reminders) == 0 {
|
|
isFinished = false
|
|
} else {
|
|
for _, r := range reminders {
|
|
channels = append(channels, r.RemindChannel)
|
|
if r.RemindStatus != 2 {
|
|
isFinished = false
|
|
}
|
|
}
|
|
}
|
|
|
|
// status filter
|
|
if status == "pending" && isFinished {
|
|
continue
|
|
}
|
|
if status == "done" && !isFinished {
|
|
continue
|
|
}
|
|
|
|
item := map[string]interface{}{
|
|
"id": s.ID,
|
|
"content": s.Content,
|
|
"schedule_time": s.ScheduleTime.Format("2006-01-02 15:04:05"),
|
|
"remind_channels": channels,
|
|
"is_finished": isFinished,
|
|
"created_at": s.ScheduleTime.Format("2006-01-02 15:04:05"),
|
|
"updated_at": s.ScheduleTime.Format("2006-01-02 15:04:05"),
|
|
}
|
|
if len(reminders) > 0 {
|
|
first := reminders[0]
|
|
item["advance_minutes"] = first.AdvanceMinutes
|
|
if !first.CreateTime.IsZero() {
|
|
item["created_at"] = first.CreateTime.Format("2006-01-02 15:04:05")
|
|
}
|
|
if !first.UpdateTime.IsZero() {
|
|
item["updated_at"] = first.UpdateTime.Format("2006-01-02 15:04:05")
|
|
}
|
|
}
|
|
list = append(list, item)
|
|
}
|
|
|
|
c.ok(map[string]interface{}{
|
|
"list": list,
|
|
"total": total,
|
|
"page": page,
|
|
"pageSize": pageSize,
|
|
})
|
|
}
|
|
|
|
// GetDetail GET /app/schedule/:id
|
|
func (c *AppReminderController) GetDetail() {
|
|
claims, err := c.appClaims()
|
|
if err != nil {
|
|
c.jsonErr(401, 401, "未登录或无权限")
|
|
return
|
|
}
|
|
|
|
idStr := c.Ctx.Input.Param(":id")
|
|
id, _ := strconv.ParseUint(idStr, 10, 64)
|
|
if id == 0 {
|
|
c.jsonErr(400, 400, "无效的ID")
|
|
return
|
|
}
|
|
|
|
var schedule models.BackendSchedule
|
|
err = models.Orm.QueryTable(new(models.BackendSchedule)).
|
|
Filter("id", id).
|
|
Filter("user_id", claims.UserID).
|
|
Filter("tid", claims.TenantId).
|
|
One(&schedule)
|
|
if err != nil {
|
|
c.jsonErr(404, 404, "日程未找到")
|
|
return
|
|
}
|
|
|
|
var reminders []models.BackendScheduleReminder
|
|
_, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
|
|
Filter("schedule_id", schedule.ID).
|
|
Filter("is_deleted", 0).
|
|
All(&reminders)
|
|
|
|
channels := make([]string, 0, len(reminders))
|
|
var first models.BackendScheduleReminder
|
|
for _, r := range reminders {
|
|
channels = append(channels, r.RemindChannel)
|
|
first = r
|
|
}
|
|
|
|
isFinished := true
|
|
if len(reminders) == 0 {
|
|
isFinished = false
|
|
} else {
|
|
for _, r := range reminders {
|
|
if r.RemindStatus != 2 {
|
|
isFinished = false
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
data := map[string]interface{}{
|
|
"id": schedule.ID,
|
|
"content": schedule.Content,
|
|
"schedule_time": schedule.ScheduleTime.Format("2006-01-02 15:04:05"),
|
|
"remind_channels": channels,
|
|
"is_finished": isFinished,
|
|
}
|
|
if first.ID > 0 {
|
|
data["advance_minutes"] = first.AdvanceMinutes
|
|
if !first.CreateTime.IsZero() {
|
|
data["created_at"] = first.CreateTime.Format("2006-01-02 15:04:05")
|
|
}
|
|
if !first.UpdateTime.IsZero() {
|
|
data["updated_at"] = first.UpdateTime.Format("2006-01-02 15:04:05")
|
|
}
|
|
}
|
|
|
|
c.ok(data)
|
|
}
|
|
|
|
// Create POST /app/schedule
|
|
func (c *AppReminderController) Create() {
|
|
claims, err := c.appClaims()
|
|
if err != nil {
|
|
c.jsonErr(401, 401, "未登录或无权限")
|
|
return
|
|
}
|
|
|
|
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
|
if err != nil {
|
|
c.jsonErr(400, 400, "参数错误")
|
|
return
|
|
}
|
|
var p appSchedulePayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
c.jsonErr(400, 400, "参数错误")
|
|
return
|
|
}
|
|
|
|
if strings.TrimSpace(p.Content) == "" {
|
|
c.jsonErr(400, 400, "日程内容不能为空")
|
|
return
|
|
}
|
|
if strings.TrimSpace(p.ScheduleTime) == "" {
|
|
c.jsonErr(400, 400, "日程发生时间不能为空")
|
|
return
|
|
}
|
|
|
|
schedTime, err := time.ParseInLocation("2006-01-02 15:04:05", p.ScheduleTime, time.Local)
|
|
if err != nil {
|
|
c.jsonErr(400, 400, "日程时间格式不合法,支持 YYYY-MM-DD HH:mm:ss")
|
|
return
|
|
}
|
|
|
|
content := strings.TrimSpace(p.Content)
|
|
title := strings.SplitN(content, "\n", 2)[0]
|
|
if len(title) > 60 {
|
|
title = title[:60]
|
|
}
|
|
if title == "" {
|
|
title = "日程提醒"
|
|
}
|
|
|
|
schedule := models.BackendSchedule{
|
|
Tid: claims.TenantId,
|
|
Title: title,
|
|
Content: content,
|
|
ScheduleTime: schedTime,
|
|
UserID: uint64(claims.UserID),
|
|
}
|
|
schedID, err := models.Orm.Insert(&schedule)
|
|
if err != nil {
|
|
c.jsonErr(500, 500, "保存日程失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
for _, ch := range p.RemindChannels {
|
|
ch = strings.ToUpper(strings.TrimSpace(ch))
|
|
if ch != "SMS" && ch != "EMAIL" && ch != "BARK" && ch != "SITE_MSG" && ch != "APP" {
|
|
continue
|
|
}
|
|
if ch == "APP" {
|
|
ch = "SITE_MSG"
|
|
}
|
|
|
|
firstSendTime := schedTime.Add(-time.Duration(p.AdvanceMinutes) * time.Minute)
|
|
|
|
reminder := models.BackendScheduleReminder{
|
|
Tid: claims.TenantId,
|
|
ScheduleID: uint64(schedID),
|
|
RemindChannel: ch,
|
|
AdvanceMinutes: p.AdvanceMinutes,
|
|
NextRemindTime: firstSendTime,
|
|
ReceiverUserID: uint64(claims.UserID),
|
|
RemindStatus: 0,
|
|
CreateTime: time.Now(),
|
|
UpdateTime: time.Now(),
|
|
}
|
|
|
|
if ch == "EMAIL" || ch == "BARK" {
|
|
token := appGenerateToken()
|
|
reminder.AckToken = &token
|
|
reminder.RepeatIntervalMinutes = p.RepeatIntervalMinutes
|
|
reminder.MaxSendCount = p.MaxSendCount
|
|
if reminder.MaxSendCount <= 0 {
|
|
reminder.MaxSendCount = 1
|
|
}
|
|
} else {
|
|
reminder.RepeatIntervalMinutes = 0
|
|
reminder.MaxSendCount = 1
|
|
}
|
|
|
|
_, err = models.Orm.Insert(&reminder)
|
|
if err != nil {
|
|
c.jsonErr(500, 500, "创建提醒失败: "+err.Error())
|
|
return
|
|
}
|
|
}
|
|
|
|
c.ok(map[string]interface{}{"id": schedID})
|
|
}
|
|
|
|
// Update PUT /app/schedule/:id
|
|
func (c *AppReminderController) Update() {
|
|
claims, err := c.appClaims()
|
|
if err != nil {
|
|
c.jsonErr(401, 401, "未登录或无权限")
|
|
return
|
|
}
|
|
|
|
idStr := c.Ctx.Input.Param(":id")
|
|
id, _ := strconv.ParseUint(idStr, 10, 64)
|
|
if id == 0 {
|
|
c.jsonErr(400, 400, "无效的ID")
|
|
return
|
|
}
|
|
|
|
raw, err := io.ReadAll(c.Ctx.Request.Body)
|
|
if err != nil {
|
|
c.jsonErr(400, 400, "参数错误")
|
|
return
|
|
}
|
|
var p appSchedulePayload
|
|
if err := json.Unmarshal(raw, &p); err != nil {
|
|
c.jsonErr(400, 400, "参数错误")
|
|
return
|
|
}
|
|
|
|
schedTime, err := time.ParseInLocation("2006-01-02 15:04:05", p.ScheduleTime, time.Local)
|
|
if err != nil {
|
|
c.jsonErr(400, 400, "日程时间格式不合法")
|
|
return
|
|
}
|
|
|
|
var schedule models.BackendSchedule
|
|
err = models.Orm.QueryTable(new(models.BackendSchedule)).
|
|
Filter("id", id).
|
|
Filter("user_id", claims.UserID).
|
|
Filter("tid", claims.TenantId).
|
|
One(&schedule)
|
|
if err != nil {
|
|
c.jsonErr(404, 404, "日程未找到")
|
|
return
|
|
}
|
|
|
|
var reminders []models.BackendScheduleReminder
|
|
_, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
|
|
Filter("schedule_id", id).
|
|
Filter("is_deleted", 0).
|
|
All(&reminders)
|
|
isFinished := true
|
|
if len(reminders) == 0 {
|
|
isFinished = false
|
|
} else {
|
|
for _, r := range reminders {
|
|
if r.RemindStatus != 2 {
|
|
isFinished = false
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if isFinished {
|
|
c.jsonErr(400, 400, "该日程提醒已全部结束,无法编辑")
|
|
return
|
|
}
|
|
|
|
content := strings.TrimSpace(p.Content)
|
|
if content == "" {
|
|
content = schedule.Content
|
|
}
|
|
title := strings.SplitN(content, "\n", 2)[0]
|
|
if len(title) > 60 {
|
|
title = title[:60]
|
|
}
|
|
|
|
schedule.Title = title
|
|
schedule.Content = content
|
|
schedule.ScheduleTime = schedTime
|
|
_, err = models.Orm.Update(&schedule, "Title", "Content", "ScheduleTime")
|
|
if err != nil {
|
|
c.jsonErr(500, 500, "更新失败")
|
|
return
|
|
}
|
|
|
|
// 软删除旧提醒,重建
|
|
_, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
|
|
Filter("schedule_id", id).
|
|
Update(map[string]interface{}{
|
|
"IsDeleted": 1,
|
|
"UpdateTime": time.Now(),
|
|
})
|
|
|
|
for _, ch := range p.RemindChannels {
|
|
ch = strings.ToUpper(strings.TrimSpace(ch))
|
|
if ch != "SMS" && ch != "EMAIL" && ch != "BARK" && ch != "SITE_MSG" && ch != "APP" {
|
|
continue
|
|
}
|
|
if ch == "APP" {
|
|
ch = "SITE_MSG"
|
|
}
|
|
|
|
firstSendTime := schedTime.Add(-time.Duration(p.AdvanceMinutes) * time.Minute)
|
|
|
|
reminder := models.BackendScheduleReminder{
|
|
Tid: claims.TenantId,
|
|
ScheduleID: id,
|
|
RemindChannel: ch,
|
|
AdvanceMinutes: p.AdvanceMinutes,
|
|
NextRemindTime: firstSendTime,
|
|
ReceiverUserID: schedule.UserID,
|
|
RemindStatus: 0,
|
|
CreateTime: time.Now(),
|
|
UpdateTime: time.Now(),
|
|
}
|
|
|
|
if ch == "EMAIL" || ch == "BARK" {
|
|
token := appGenerateToken()
|
|
reminder.AckToken = &token
|
|
reminder.RepeatIntervalMinutes = p.RepeatIntervalMinutes
|
|
reminder.MaxSendCount = p.MaxSendCount
|
|
if reminder.MaxSendCount <= 0 {
|
|
reminder.MaxSendCount = 1
|
|
}
|
|
} else {
|
|
reminder.RepeatIntervalMinutes = 0
|
|
reminder.MaxSendCount = 1
|
|
}
|
|
|
|
_, err = models.Orm.Insert(&reminder)
|
|
if err != nil {
|
|
c.jsonErr(500, 500, "重新创建提醒失败")
|
|
return
|
|
}
|
|
}
|
|
|
|
c.ok(nil)
|
|
}
|
|
|
|
// Delete DELETE /app/schedule/:id
|
|
func (c *AppReminderController) Delete() {
|
|
claims, err := c.appClaims()
|
|
if err != nil {
|
|
c.jsonErr(401, 401, "未登录或无权限")
|
|
return
|
|
}
|
|
|
|
idStr := c.Ctx.Input.Param(":id")
|
|
id, _ := strconv.ParseUint(idStr, 10, 64)
|
|
if id == 0 {
|
|
c.jsonErr(400, 400, "无效的ID")
|
|
return
|
|
}
|
|
|
|
var schedule models.BackendSchedule
|
|
err = models.Orm.QueryTable(new(models.BackendSchedule)).
|
|
Filter("id", id).
|
|
Filter("user_id", claims.UserID).
|
|
Filter("tid", claims.TenantId).
|
|
One(&schedule)
|
|
if err != nil {
|
|
c.jsonErr(404, 404, "日程未找到")
|
|
return
|
|
}
|
|
|
|
var reminders []models.BackendScheduleReminder
|
|
_, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
|
|
Filter("schedule_id", id).
|
|
Filter("is_deleted", 0).
|
|
All(&reminders)
|
|
isFinished := true
|
|
if len(reminders) == 0 {
|
|
isFinished = false
|
|
} else {
|
|
for _, r := range reminders {
|
|
if r.RemindStatus != 2 {
|
|
isFinished = false
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if isFinished {
|
|
c.jsonErr(400, 400, "该日程提醒已全部结束,无法删除")
|
|
return
|
|
}
|
|
|
|
_, err = models.Orm.QueryTable(new(models.BackendSchedule)).Filter("id", id).Delete()
|
|
if err == nil {
|
|
_, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
|
|
Filter("schedule_id", id).
|
|
Update(map[string]interface{}{
|
|
"IsDeleted": 1,
|
|
"UpdateTime": time.Now(),
|
|
})
|
|
}
|
|
|
|
middleware.WriteDeleteLog(uint64(claims.UserID), claims.TenantId, "schedule", schedule.Content)
|
|
c.ok(nil)
|
|
}
|
|
|
|
// ToggleComplete POST /app/schedule/:id/toggle
|
|
func (c *AppReminderController) ToggleComplete() {
|
|
claims, err := c.appClaims()
|
|
if err != nil {
|
|
c.jsonErr(401, 401, "未登录或无权限")
|
|
return
|
|
}
|
|
|
|
idStr := c.Ctx.Input.Param(":id")
|
|
id, _ := strconv.ParseUint(idStr, 10, 64)
|
|
if id == 0 {
|
|
c.jsonErr(400, 400, "无效的ID")
|
|
return
|
|
}
|
|
|
|
var schedule models.BackendSchedule
|
|
err = models.Orm.QueryTable(new(models.BackendSchedule)).
|
|
Filter("id", id).
|
|
Filter("user_id", claims.UserID).
|
|
Filter("tid", claims.TenantId).
|
|
One(&schedule)
|
|
if err != nil {
|
|
c.jsonErr(404, 404, "日程未找到")
|
|
return
|
|
}
|
|
|
|
var reminders []models.BackendScheduleReminder
|
|
_, _ = models.Orm.QueryTable(new(models.BackendScheduleReminder)).
|
|
Filter("schedule_id", id).
|
|
Filter("is_deleted", 0).
|
|
All(&reminders)
|
|
|
|
// Toggle: if all finished -> set to pending; else -> set to finished
|
|
allFinished := len(reminders) > 0
|
|
for _, r := range reminders {
|
|
if r.RemindStatus != 2 {
|
|
allFinished = false
|
|
break
|
|
}
|
|
}
|
|
|
|
var newStatus int8
|
|
if allFinished {
|
|
newStatus = 0 // reopen
|
|
} else {
|
|
newStatus = 2 // mark done
|
|
}
|
|
|
|
now := time.Now()
|
|
for _, r := range reminders {
|
|
r.RemindStatus = newStatus
|
|
r.UpdateTime = now
|
|
models.Orm.Update(&r, "RemindStatus", "UpdateTime")
|
|
}
|
|
|
|
c.ok(map[string]interface{}{
|
|
"is_finished": newStatus == 2,
|
|
})
|
|
}
|