繰り返し課題のAPI管理UIを追加、課題一覧のUX向上
This commit is contained in:
@@ -13,11 +13,13 @@ import (
|
||||
|
||||
type APIHandler struct {
|
||||
assignmentService *service.AssignmentService
|
||||
recurringService *service.RecurringAssignmentService
|
||||
}
|
||||
|
||||
func NewAPIHandler() *APIHandler {
|
||||
return &APIHandler{
|
||||
assignmentService: service.NewAssignmentService(),
|
||||
recurringService: service.NewRecurringAssignmentService(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,17 +28,12 @@ func (h *APIHandler) getUserID(c *gin.Context) uint {
|
||||
return userID.(uint)
|
||||
}
|
||||
|
||||
// ListAssignments returns all assignments for the authenticated user with pagination
|
||||
// GET /api/v1/assignments?filter=pending&page=1&page_size=20
|
||||
func (h *APIHandler) ListAssignments(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
filter := c.Query("filter") // pending, completed, overdue
|
||||
|
||||
// Parse pagination parameters
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
|
||||
// Validate pagination parameters
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
@@ -44,10 +41,9 @@ func (h *APIHandler) ListAssignments(c *gin.Context) {
|
||||
pageSize = 20
|
||||
}
|
||||
if pageSize > 100 {
|
||||
pageSize = 100 // Maximum page size to prevent abuse
|
||||
pageSize = 100
|
||||
}
|
||||
|
||||
// Use paginated methods for filtered queries
|
||||
switch filter {
|
||||
case "completed":
|
||||
result, err := h.assignmentService.GetCompletedByUserPaginated(userID, page, pageSize)
|
||||
@@ -56,12 +52,12 @@ func (h *APIHandler) ListAssignments(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"assignments": result.Assignments,
|
||||
"count": len(result.Assignments),
|
||||
"total_count": result.TotalCount,
|
||||
"total_pages": result.TotalPages,
|
||||
"current_page": result.CurrentPage,
|
||||
"page_size": result.PageSize,
|
||||
"assignments": result.Assignments,
|
||||
"count": len(result.Assignments),
|
||||
"total_count": result.TotalCount,
|
||||
"total_pages": result.TotalPages,
|
||||
"current_page": result.CurrentPage,
|
||||
"page_size": result.PageSize,
|
||||
})
|
||||
return
|
||||
case "overdue":
|
||||
@@ -71,12 +67,12 @@ func (h *APIHandler) ListAssignments(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"assignments": result.Assignments,
|
||||
"count": len(result.Assignments),
|
||||
"total_count": result.TotalCount,
|
||||
"total_pages": result.TotalPages,
|
||||
"current_page": result.CurrentPage,
|
||||
"page_size": result.PageSize,
|
||||
"assignments": result.Assignments,
|
||||
"count": len(result.Assignments),
|
||||
"total_count": result.TotalCount,
|
||||
"total_pages": result.TotalPages,
|
||||
"current_page": result.CurrentPage,
|
||||
"page_size": result.PageSize,
|
||||
})
|
||||
return
|
||||
case "pending":
|
||||
@@ -86,23 +82,21 @@ func (h *APIHandler) ListAssignments(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"assignments": result.Assignments,
|
||||
"count": len(result.Assignments),
|
||||
"total_count": result.TotalCount,
|
||||
"total_pages": result.TotalPages,
|
||||
"current_page": result.CurrentPage,
|
||||
"page_size": result.PageSize,
|
||||
"assignments": result.Assignments,
|
||||
"count": len(result.Assignments),
|
||||
"total_count": result.TotalCount,
|
||||
"total_pages": result.TotalPages,
|
||||
"current_page": result.CurrentPage,
|
||||
"page_size": result.PageSize,
|
||||
})
|
||||
return
|
||||
default:
|
||||
// For "all" filter, use simple pagination without a dedicated method
|
||||
assignments, err := h.assignmentService.GetAllByUser(userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch assignments"})
|
||||
return
|
||||
}
|
||||
|
||||
// Manual pagination for all assignments
|
||||
totalCount := len(assignments)
|
||||
totalPages := (totalCount + pageSize - 1) / pageSize
|
||||
start := (page - 1) * pageSize
|
||||
@@ -115,18 +109,16 @@ func (h *APIHandler) ListAssignments(c *gin.Context) {
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"assignments": assignments[start:end],
|
||||
"count": end - start,
|
||||
"total_count": totalCount,
|
||||
"total_pages": totalPages,
|
||||
"current_page": page,
|
||||
"page_size": pageSize,
|
||||
"assignments": assignments[start:end],
|
||||
"count": end - start,
|
||||
"total_count": totalCount,
|
||||
"total_pages": totalPages,
|
||||
"current_page": page,
|
||||
"page_size": pageSize,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ListPendingAssignments returns pending assignments with pagination
|
||||
// GET /api/v1/assignments/pending?page=1&page_size=20
|
||||
func (h *APIHandler) ListPendingAssignments(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
page, pageSize := h.parsePagination(c)
|
||||
@@ -140,8 +132,6 @@ func (h *APIHandler) ListPendingAssignments(c *gin.Context) {
|
||||
h.sendPaginatedResponse(c, result)
|
||||
}
|
||||
|
||||
// ListCompletedAssignments returns completed assignments with pagination
|
||||
// GET /api/v1/assignments/completed?page=1&page_size=20
|
||||
func (h *APIHandler) ListCompletedAssignments(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
page, pageSize := h.parsePagination(c)
|
||||
@@ -155,8 +145,6 @@ func (h *APIHandler) ListCompletedAssignments(c *gin.Context) {
|
||||
h.sendPaginatedResponse(c, result)
|
||||
}
|
||||
|
||||
// ListOverdueAssignments returns overdue assignments with pagination
|
||||
// GET /api/v1/assignments/overdue?page=1&page_size=20
|
||||
func (h *APIHandler) ListOverdueAssignments(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
page, pageSize := h.parsePagination(c)
|
||||
@@ -170,8 +158,6 @@ func (h *APIHandler) ListOverdueAssignments(c *gin.Context) {
|
||||
h.sendPaginatedResponse(c, result)
|
||||
}
|
||||
|
||||
// ListDueTodayAssignments returns assignments due today
|
||||
// GET /api/v1/assignments/due-today
|
||||
func (h *APIHandler) ListDueTodayAssignments(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
|
||||
@@ -187,8 +173,6 @@ func (h *APIHandler) ListDueTodayAssignments(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// ListDueThisWeekAssignments returns assignments due within this week
|
||||
// GET /api/v1/assignments/due-this-week
|
||||
func (h *APIHandler) ListDueThisWeekAssignments(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
|
||||
@@ -204,7 +188,6 @@ func (h *APIHandler) ListDueThisWeekAssignments(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// parsePagination extracts and validates pagination parameters
|
||||
func (h *APIHandler) parsePagination(c *gin.Context) (page int, pageSize int) {
|
||||
page, _ = strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ = strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||||
@@ -221,7 +204,6 @@ func (h *APIHandler) parsePagination(c *gin.Context) (page int, pageSize int) {
|
||||
return page, pageSize
|
||||
}
|
||||
|
||||
// sendPaginatedResponse sends a standard paginated JSON response
|
||||
func (h *APIHandler) sendPaginatedResponse(c *gin.Context, result *service.PaginatedResult) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"assignments": result.Assignments,
|
||||
@@ -233,8 +215,6 @@ func (h *APIHandler) sendPaginatedResponse(c *gin.Context, result *service.Pagin
|
||||
})
|
||||
}
|
||||
|
||||
// GetAssignment returns a single assignment by ID
|
||||
// GET /api/v1/assignments/:id
|
||||
func (h *APIHandler) GetAssignment(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
@@ -252,40 +232,121 @@ func (h *APIHandler) GetAssignment(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, assignment)
|
||||
}
|
||||
|
||||
// CreateAssignmentInput represents the JSON input for creating an assignment
|
||||
type CreateAssignmentInput struct {
|
||||
Title string `json:"title" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Subject string `json:"subject"`
|
||||
Priority string `json:"priority"` // low, medium, high (default: medium)
|
||||
DueDate string `json:"due_date" binding:"required"` // RFC3339 or 2006-01-02T15:04
|
||||
Priority string `json:"priority"`
|
||||
DueDate string `json:"due_date" binding:"required"`
|
||||
|
||||
ReminderEnabled bool `json:"reminder_enabled"`
|
||||
ReminderAt string `json:"reminder_at"`
|
||||
UrgentReminderEnabled *bool `json:"urgent_reminder_enabled"`
|
||||
Recurrence struct {
|
||||
Type string `json:"type"`
|
||||
Interval int `json:"interval"`
|
||||
Weekday interface{} `json:"weekday"`
|
||||
Day interface{} `json:"day"`
|
||||
Until struct {
|
||||
Type string `json:"type"`
|
||||
Count int `json:"count"`
|
||||
Date string `json:"date"`
|
||||
} `json:"until"`
|
||||
} `json:"recurrence"`
|
||||
}
|
||||
|
||||
// CreateAssignment creates a new assignment
|
||||
// POST /api/v1/assignments
|
||||
func (h *APIHandler) CreateAssignment(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
|
||||
var input CreateAssignmentInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input: title and due_date are required"})
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
dueDate, err := time.Parse(time.RFC3339, input.DueDate)
|
||||
dueDate, err := parseDateString(input.DueDate)
|
||||
if err != nil {
|
||||
dueDate, err = time.ParseInLocation("2006-01-02T15:04", input.DueDate, time.Local)
|
||||
if err != nil {
|
||||
dueDate, err = time.ParseInLocation("2006-01-02", input.DueDate, time.Local)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid due_date format. Use RFC3339 or 2006-01-02T15:04"})
|
||||
return
|
||||
}
|
||||
dueDate = dueDate.Add(23*time.Hour + 59*time.Minute)
|
||||
}
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid due_date format. Use RFC3339 or 2006-01-02T15:04"})
|
||||
return
|
||||
}
|
||||
|
||||
assignment, err := h.assignmentService.Create(userID, input.Title, input.Description, input.Subject, input.Priority, dueDate, false, nil, true)
|
||||
var reminderAt *time.Time
|
||||
if input.ReminderEnabled && input.ReminderAt != "" {
|
||||
reminderTime, err := parseDateString(input.ReminderAt)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid reminder_at format"})
|
||||
return
|
||||
}
|
||||
reminderAt = &reminderTime
|
||||
}
|
||||
|
||||
urgentReminder := true
|
||||
if input.UrgentReminderEnabled != nil {
|
||||
urgentReminder = *input.UrgentReminderEnabled
|
||||
}
|
||||
|
||||
if input.Recurrence.Type != "" && input.Recurrence.Type != "none" {
|
||||
serviceInput := service.CreateRecurringAssignmentInput{
|
||||
Title: input.Title,
|
||||
Description: input.Description,
|
||||
Subject: input.Subject,
|
||||
Priority: input.Priority,
|
||||
FirstDueDate: dueDate,
|
||||
DueTime: dueDate.Format("15:04"),
|
||||
RecurrenceType: input.Recurrence.Type,
|
||||
RecurrenceInterval: input.Recurrence.Interval,
|
||||
ReminderEnabled: input.ReminderEnabled,
|
||||
ReminderOffset: nil,
|
||||
UrgentReminderEnabled: urgentReminder,
|
||||
}
|
||||
|
||||
if serviceInput.RecurrenceInterval < 1 {
|
||||
serviceInput.RecurrenceInterval = 1
|
||||
}
|
||||
|
||||
if input.Recurrence.Weekday != nil {
|
||||
if wd, ok := input.Recurrence.Weekday.(float64); ok {
|
||||
wdInt := int(wd)
|
||||
serviceInput.RecurrenceWeekday = &wdInt
|
||||
}
|
||||
}
|
||||
|
||||
if input.Recurrence.Day != nil {
|
||||
if d, ok := input.Recurrence.Day.(float64); ok {
|
||||
dInt := int(d)
|
||||
serviceInput.RecurrenceDay = &dInt
|
||||
}
|
||||
}
|
||||
|
||||
serviceInput.EndType = input.Recurrence.Until.Type
|
||||
if serviceInput.EndType == "" {
|
||||
serviceInput.EndType = "never"
|
||||
}
|
||||
|
||||
if serviceInput.EndType == "count" {
|
||||
count := input.Recurrence.Until.Count
|
||||
serviceInput.EndCount = &count
|
||||
} else if serviceInput.EndType == "date" && input.Recurrence.Until.Date != "" {
|
||||
endDate, err := parseDateString(input.Recurrence.Until.Date)
|
||||
if err == nil {
|
||||
serviceInput.EndDate = &endDate
|
||||
}
|
||||
}
|
||||
|
||||
recurring, err := h.recurringService.Create(userID, serviceInput)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create recurring assignment: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"message": "Recurring assignment created",
|
||||
"recurring_assignment": recurring,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
assignment, err := h.assignmentService.Create(userID, input.Title, input.Description, input.Subject, input.Priority, dueDate, input.ReminderEnabled, reminderAt, urgentReminder)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create assignment"})
|
||||
return
|
||||
@@ -294,17 +355,17 @@ func (h *APIHandler) CreateAssignment(c *gin.Context) {
|
||||
c.JSON(http.StatusCreated, assignment)
|
||||
}
|
||||
|
||||
// UpdateAssignmentInput represents the JSON input for updating an assignment
|
||||
type UpdateAssignmentInput struct {
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Subject string `json:"subject"`
|
||||
Priority string `json:"priority"`
|
||||
DueDate string `json:"due_date"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Subject string `json:"subject"`
|
||||
Priority string `json:"priority"`
|
||||
DueDate string `json:"due_date"`
|
||||
ReminderEnabled *bool `json:"reminder_enabled"`
|
||||
ReminderAt string `json:"reminder_at"`
|
||||
UrgentReminderEnabled *bool `json:"urgent_reminder_enabled"`
|
||||
}
|
||||
|
||||
// UpdateAssignment updates an existing assignment
|
||||
// PUT /api/v1/assignments/:id
|
||||
func (h *APIHandler) UpdateAssignment(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
@@ -313,7 +374,6 @@ func (h *APIHandler) UpdateAssignment(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get existing assignment
|
||||
existing, err := h.assignmentService.GetByID(userID, uint(id))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Assignment not found"})
|
||||
@@ -326,14 +386,21 @@ func (h *APIHandler) UpdateAssignment(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Use existing values if not provided
|
||||
title := input.Title
|
||||
if title == "" {
|
||||
title = existing.Title
|
||||
}
|
||||
|
||||
description := input.Description
|
||||
if description == "" {
|
||||
description = existing.Description
|
||||
}
|
||||
|
||||
subject := input.Subject
|
||||
if subject == "" {
|
||||
subject = existing.Subject
|
||||
}
|
||||
|
||||
priority := input.Priority
|
||||
if priority == "" {
|
||||
priority = existing.Priority
|
||||
@@ -341,18 +408,36 @@ func (h *APIHandler) UpdateAssignment(c *gin.Context) {
|
||||
|
||||
dueDate := existing.DueDate
|
||||
if input.DueDate != "" {
|
||||
dueDate, err = time.Parse(time.RFC3339, input.DueDate)
|
||||
parsedDate, err := parseDateString(input.DueDate)
|
||||
if err != nil {
|
||||
dueDate, err = time.ParseInLocation("2006-01-02T15:04", input.DueDate, time.Local)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid due_date format"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid due_date format"})
|
||||
return
|
||||
}
|
||||
dueDate = parsedDate
|
||||
}
|
||||
|
||||
// Preserve existing reminder settings for API updates
|
||||
assignment, err := h.assignmentService.Update(userID, uint(id), title, description, subject, priority, dueDate, existing.ReminderEnabled, existing.ReminderAt, existing.UrgentReminderEnabled)
|
||||
reminderEnabled := existing.ReminderEnabled
|
||||
if input.ReminderEnabled != nil {
|
||||
reminderEnabled = *input.ReminderEnabled
|
||||
}
|
||||
|
||||
reminderAt := existing.ReminderAt
|
||||
if input.ReminderAt != "" {
|
||||
parsedReminderAt, err := parseDateString(input.ReminderAt)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid reminder_at format"})
|
||||
return
|
||||
}
|
||||
reminderAt = &parsedReminderAt
|
||||
} else if input.ReminderEnabled != nil && !*input.ReminderEnabled {
|
||||
}
|
||||
|
||||
urgentReminderEnabled := existing.UrgentReminderEnabled
|
||||
if input.UrgentReminderEnabled != nil {
|
||||
urgentReminderEnabled = *input.UrgentReminderEnabled
|
||||
}
|
||||
|
||||
assignment, err := h.assignmentService.Update(userID, uint(id), title, description, subject, priority, dueDate, reminderEnabled, reminderAt, urgentReminderEnabled)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update assignment"})
|
||||
return
|
||||
@@ -361,8 +446,6 @@ func (h *APIHandler) UpdateAssignment(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, assignment)
|
||||
}
|
||||
|
||||
// DeleteAssignment deletes an assignment
|
||||
// DELETE /api/v1/assignments/:id
|
||||
func (h *APIHandler) DeleteAssignment(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
@@ -371,6 +454,26 @@ func (h *APIHandler) DeleteAssignment(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
deleteRecurring := c.Query("delete_recurring") == "true"
|
||||
|
||||
if deleteRecurring {
|
||||
|
||||
assignment, err := h.assignmentService.GetByID(userID, uint(id))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Assignment not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if assignment.RecurringAssignmentID != nil {
|
||||
if err := h.recurringService.Delete(userID, *assignment.RecurringAssignmentID, false); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete recurring assignment"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Assignment and recurring settings deleted"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.assignmentService.Delete(userID, uint(id)); err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Assignment not found"})
|
||||
return
|
||||
@@ -379,8 +482,6 @@ func (h *APIHandler) DeleteAssignment(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Assignment deleted"})
|
||||
}
|
||||
|
||||
// ToggleAssignment toggles the completion status of an assignment
|
||||
// PATCH /api/v1/assignments/:id/toggle
|
||||
func (h *APIHandler) ToggleAssignment(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
@@ -398,18 +499,14 @@ func (h *APIHandler) ToggleAssignment(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, assignment)
|
||||
}
|
||||
|
||||
// GetStatistics returns statistics for the authenticated user
|
||||
// GET /api/v1/statistics?subject=数学&from=2025-01-01&to=2025-12-31&include_archived=true
|
||||
func (h *APIHandler) GetStatistics(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
|
||||
// Parse filter parameters
|
||||
filter := service.StatisticsFilter{
|
||||
Subject: c.Query("subject"),
|
||||
IncludeArchived: c.Query("include_archived") == "true",
|
||||
}
|
||||
|
||||
// Parse from date
|
||||
if fromStr := c.Query("from"); fromStr != "" {
|
||||
fromDate, err := time.ParseInLocation("2006-01-02", fromStr, time.Local)
|
||||
if err != nil {
|
||||
@@ -419,7 +516,6 @@ func (h *APIHandler) GetStatistics(c *gin.Context) {
|
||||
filter.From = &fromDate
|
||||
}
|
||||
|
||||
// Parse to date
|
||||
if toStr := c.Query("to"); toStr != "" {
|
||||
toDate, err := time.ParseInLocation("2006-01-02", toStr, time.Local)
|
||||
if err != nil {
|
||||
@@ -438,3 +534,18 @@ func (h *APIHandler) GetStatistics(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, stats)
|
||||
}
|
||||
|
||||
func parseDateString(dateStr string) (time.Time, error) {
|
||||
t, err := time.Parse(time.RFC3339, dateStr)
|
||||
if err == nil {
|
||||
return t, nil
|
||||
}
|
||||
t, err = time.ParseInLocation("2006-01-02T15:04", dateStr, time.Local)
|
||||
if err == nil {
|
||||
return t, nil
|
||||
}
|
||||
t, err = time.ParseInLocation("2006-01-02", dateStr, time.Local)
|
||||
if err == nil {
|
||||
return t.Add(23*time.Hour + 59*time.Minute), nil
|
||||
}
|
||||
return time.Time{}, err
|
||||
}
|
||||
|
||||
167
internal/handler/api_recurring_handler.go
Normal file
167
internal/handler/api_recurring_handler.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"homework-manager/internal/middleware"
|
||||
"homework-manager/internal/models"
|
||||
"homework-manager/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type APIRecurringHandler struct {
|
||||
recurringService *service.RecurringAssignmentService
|
||||
}
|
||||
|
||||
func NewAPIRecurringHandler() *APIRecurringHandler {
|
||||
return &APIRecurringHandler{
|
||||
recurringService: service.NewRecurringAssignmentService(),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *APIRecurringHandler) getUserID(c *gin.Context) uint {
|
||||
userID, _ := c.Get(middleware.UserIDKey)
|
||||
return userID.(uint)
|
||||
}
|
||||
|
||||
func (h *APIRecurringHandler) ListRecurring(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
|
||||
recurringList, err := h.recurringService.GetAllByUser(userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch recurring assignments"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"recurring_assignments": recurringList,
|
||||
"count": len(recurringList),
|
||||
})
|
||||
}
|
||||
|
||||
func (h *APIRecurringHandler) GetRecurring(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
}
|
||||
|
||||
recurring, err := h.recurringService.GetByID(userID, uint(id))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Recurring assignment not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, recurring)
|
||||
}
|
||||
|
||||
type UpdateRecurringAPIInput struct {
|
||||
Title *string `json:"title"`
|
||||
Description *string `json:"description"`
|
||||
Subject *string `json:"subject"`
|
||||
Priority *string `json:"priority"`
|
||||
RecurrenceType *string `json:"recurrence_type"`
|
||||
RecurrenceInterval *int `json:"recurrence_interval"`
|
||||
RecurrenceWeekday *int `json:"recurrence_weekday"`
|
||||
RecurrenceDay *int `json:"recurrence_day"`
|
||||
DueTime *string `json:"due_time"`
|
||||
EndType *string `json:"end_type"`
|
||||
EndCount *int `json:"end_count"`
|
||||
EndDate *string `json:"end_date"` // YYYY-MM-DD
|
||||
IsActive *bool `json:"is_active"` // To stop/resume
|
||||
ReminderEnabled *bool `json:"reminder_enabled"`
|
||||
ReminderOffset *int `json:"reminder_offset"`
|
||||
UrgentReminderEnabled *bool `json:"urgent_reminder_enabled"`
|
||||
EditBehavior string `json:"edit_behavior"` // this_only, this_and_future, all (default: this_only)
|
||||
}
|
||||
|
||||
func (h *APIRecurringHandler) UpdateRecurring(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var input UpdateRecurringAPIInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
existing, err := h.recurringService.GetByID(userID, uint(id))
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Recurring assignment not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if input.IsActive != nil {
|
||||
if err := h.recurringService.SetActive(userID, uint(id), *input.IsActive); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update active status"})
|
||||
return
|
||||
}
|
||||
existing.IsActive = *input.IsActive
|
||||
}
|
||||
|
||||
serviceInput := service.UpdateRecurringInput{
|
||||
Title: input.Title,
|
||||
Description: input.Description,
|
||||
Subject: input.Subject,
|
||||
Priority: input.Priority,
|
||||
RecurrenceType: input.RecurrenceType,
|
||||
RecurrenceInterval: input.RecurrenceInterval,
|
||||
RecurrenceWeekday: input.RecurrenceWeekday,
|
||||
RecurrenceDay: input.RecurrenceDay,
|
||||
DueTime: input.DueTime,
|
||||
EndType: input.EndType,
|
||||
EndCount: input.EndCount,
|
||||
EditBehavior: input.EditBehavior,
|
||||
ReminderEnabled: input.ReminderEnabled,
|
||||
ReminderOffset: input.ReminderOffset,
|
||||
UrgentReminderEnabled: input.UrgentReminderEnabled,
|
||||
}
|
||||
|
||||
if input.EndDate != nil && *input.EndDate != "" {
|
||||
endDate, err := parseDateString(*input.EndDate)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid end_date format"})
|
||||
return
|
||||
}
|
||||
serviceInput.EndDate = &endDate
|
||||
}
|
||||
|
||||
if serviceInput.EditBehavior == "" {
|
||||
serviceInput.EditBehavior = models.EditBehaviorThisOnly
|
||||
}
|
||||
|
||||
updated, err := h.recurringService.Update(userID, uint(id), serviceInput)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update recurring assignment"})
|
||||
return
|
||||
}
|
||||
|
||||
updated.IsActive = existing.IsActive
|
||||
|
||||
c.JSON(http.StatusOK, updated)
|
||||
}
|
||||
|
||||
func (h *APIRecurringHandler) DeleteRecurring(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid ID"})
|
||||
return
|
||||
}
|
||||
|
||||
err = h.recurringService.Delete(userID, uint(id), false)
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Recurring assignment not found or failed to delete"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Recurring assignment deleted"})
|
||||
}
|
||||
@@ -16,12 +16,14 @@ import (
|
||||
type AssignmentHandler struct {
|
||||
assignmentService *service.AssignmentService
|
||||
notificationService *service.NotificationService
|
||||
recurringService *service.RecurringAssignmentService
|
||||
}
|
||||
|
||||
func NewAssignmentHandler(notificationService *service.NotificationService) *AssignmentHandler {
|
||||
return &AssignmentHandler{
|
||||
assignmentService: service.NewAssignmentService(),
|
||||
notificationService: notificationService,
|
||||
recurringService: service.NewRecurringAssignmentService(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,11 +106,14 @@ func (h *AssignmentHandler) Index(c *gin.Context) {
|
||||
func (h *AssignmentHandler) New(c *gin.Context) {
|
||||
role, _ := c.Get(middleware.UserRoleKey)
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
now := time.Now()
|
||||
|
||||
RenderHTML(c, http.StatusOK, "assignments/new.html", gin.H{
|
||||
"title": "課題登録",
|
||||
"isAdmin": role == "admin",
|
||||
"userName": name,
|
||||
"title": "課題登録",
|
||||
"isAdmin": role == "admin",
|
||||
"userName": name,
|
||||
"currentWeekday": int(now.Weekday()),
|
||||
"currentDay": now.Day(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -196,7 +201,7 @@ func (h *AssignmentHandler) Create(c *gin.Context) {
|
||||
dueTime := dueDate.Format("15:04")
|
||||
|
||||
recurringService := service.NewRecurringAssignmentService()
|
||||
input := service.CreateRecurringInput{
|
||||
input := service.CreateRecurringAssignmentInput{
|
||||
Title: title,
|
||||
Description: description,
|
||||
Subject: subject,
|
||||
@@ -266,12 +271,18 @@ func (h *AssignmentHandler) Edit(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
var recurring *models.RecurringAssignment
|
||||
if assignment.RecurringAssignmentID != nil {
|
||||
recurring, _ = h.recurringService.GetByID(userID, *assignment.RecurringAssignmentID)
|
||||
}
|
||||
|
||||
role, _ := c.Get(middleware.UserRoleKey)
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
|
||||
RenderHTML(c, http.StatusOK, "assignments/edit.html", gin.H{
|
||||
"title": "課題編集",
|
||||
"assignment": assignment,
|
||||
"recurring": recurring,
|
||||
"isAdmin": role == "admin",
|
||||
"userName": name,
|
||||
})
|
||||
@@ -333,6 +344,14 @@ func (h *AssignmentHandler) Delete(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
|
||||
deleteRecurringStr := c.Query("stop_recurring")
|
||||
if deleteRecurringStr != "" {
|
||||
recurringID, err := strconv.ParseUint(deleteRecurringStr, 10, 32)
|
||||
if err == nil {
|
||||
h.recurringService.Delete(userID, uint(recurringID), false)
|
||||
}
|
||||
}
|
||||
|
||||
h.assignmentService.Delete(userID, uint(id))
|
||||
|
||||
c.Redirect(http.StatusFound, "/assignments")
|
||||
@@ -417,3 +436,162 @@ func (h *AssignmentHandler) UnarchiveSubject(c *gin.Context) {
|
||||
|
||||
c.Redirect(http.StatusFound, "/statistics?include_archived=true")
|
||||
}
|
||||
|
||||
func (h *AssignmentHandler) StopRecurring(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.Redirect(http.StatusFound, "/assignments")
|
||||
return
|
||||
}
|
||||
|
||||
h.recurringService.SetActive(userID, uint(id), false)
|
||||
c.Redirect(http.StatusFound, "/assignments")
|
||||
}
|
||||
|
||||
func (h *AssignmentHandler) ResumeRecurring(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.Redirect(http.StatusFound, "/assignments")
|
||||
return
|
||||
}
|
||||
|
||||
h.recurringService.SetActive(userID, uint(id), true)
|
||||
referer := c.Request.Referer()
|
||||
if referer == "" {
|
||||
referer = "/assignments"
|
||||
}
|
||||
c.Redirect(http.StatusFound, referer)
|
||||
}
|
||||
|
||||
func (h *AssignmentHandler) ListRecurring(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
role, _ := c.Get(middleware.UserRoleKey)
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
|
||||
recurrings, err := h.recurringService.GetAllByUser(userID)
|
||||
if err != nil {
|
||||
recurrings = []models.RecurringAssignment{}
|
||||
}
|
||||
|
||||
RenderHTML(c, http.StatusOK, "recurring/index.html", gin.H{
|
||||
"title": "繰り返し設定一覧",
|
||||
"recurrings": recurrings,
|
||||
"isAdmin": role == "admin",
|
||||
"userName": name,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AssignmentHandler) EditRecurring(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.Redirect(http.StatusFound, "/assignments")
|
||||
return
|
||||
}
|
||||
|
||||
recurring, err := h.recurringService.GetByID(userID, uint(id))
|
||||
if err != nil {
|
||||
c.Redirect(http.StatusFound, "/assignments")
|
||||
return
|
||||
}
|
||||
|
||||
role, _ := c.Get(middleware.UserRoleKey)
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
|
||||
RenderHTML(c, http.StatusOK, "recurring/edit.html", gin.H{
|
||||
"title": "繰り返し課題の編集",
|
||||
"recurring": recurring,
|
||||
"isAdmin": role == "admin",
|
||||
"userName": name,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AssignmentHandler) UpdateRecurring(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.Redirect(http.StatusFound, "/assignments")
|
||||
return
|
||||
}
|
||||
|
||||
title := c.PostForm("title")
|
||||
description := c.PostForm("description")
|
||||
subject := c.PostForm("subject")
|
||||
priority := c.PostForm("priority")
|
||||
recurrenceType := c.PostForm("recurrence_type")
|
||||
dueTime := c.PostForm("due_time")
|
||||
editBehavior := c.PostForm("edit_behavior")
|
||||
|
||||
recurrenceInterval := 1
|
||||
if v, err := strconv.Atoi(c.PostForm("recurrence_interval")); err == nil && v > 0 {
|
||||
recurrenceInterval = v
|
||||
}
|
||||
|
||||
var recurrenceWeekday *int
|
||||
if wd := c.PostForm("recurrence_weekday"); wd != "" {
|
||||
if v, err := strconv.Atoi(wd); err == nil && v >= 0 && v <= 6 {
|
||||
recurrenceWeekday = &v
|
||||
}
|
||||
}
|
||||
|
||||
var recurrenceDay *int
|
||||
if d := c.PostForm("recurrence_day"); d != "" {
|
||||
if v, err := strconv.Atoi(d); err == nil && v >= 1 && v <= 31 {
|
||||
recurrenceDay = &v
|
||||
}
|
||||
}
|
||||
|
||||
endType := c.PostForm("end_type")
|
||||
var endCount *int
|
||||
if ec := c.PostForm("end_count"); ec != "" {
|
||||
if v, err := strconv.Atoi(ec); err == nil && v > 0 {
|
||||
endCount = &v
|
||||
}
|
||||
}
|
||||
|
||||
var endDate *time.Time
|
||||
if ed := c.PostForm("end_date"); ed != "" {
|
||||
if v, err := time.ParseInLocation("2006-01-02", ed, time.Local); err == nil {
|
||||
endDate = &v
|
||||
}
|
||||
}
|
||||
|
||||
input := service.UpdateRecurringInput{
|
||||
Title: &title,
|
||||
Description: &description,
|
||||
Subject: &subject,
|
||||
Priority: &priority,
|
||||
RecurrenceType: &recurrenceType,
|
||||
RecurrenceInterval: &recurrenceInterval,
|
||||
RecurrenceWeekday: recurrenceWeekday,
|
||||
RecurrenceDay: recurrenceDay,
|
||||
DueTime: &dueTime,
|
||||
EndType: &endType,
|
||||
EndCount: endCount,
|
||||
EndDate: endDate,
|
||||
EditBehavior: editBehavior,
|
||||
}
|
||||
|
||||
_, err = h.recurringService.Update(userID, uint(id), input)
|
||||
if err != nil {
|
||||
c.Redirect(http.StatusFound, "/recurring/"+c.Param("id")+"/edit")
|
||||
return
|
||||
}
|
||||
|
||||
c.Redirect(http.StatusFound, "/assignments")
|
||||
}
|
||||
|
||||
func (h *AssignmentHandler) DeleteRecurring(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.Redirect(http.StatusFound, "/assignments")
|
||||
return
|
||||
}
|
||||
|
||||
h.recurringService.Delete(userID, uint(id), false)
|
||||
|
||||
c.Redirect(http.StatusFound, "/assignments")
|
||||
}
|
||||
|
||||
@@ -346,3 +346,59 @@ func (r *AssignmentRepository) GetSubjectsByUserIDWithArchived(userID uint, incl
|
||||
err := query.Distinct("subject").Pluck("subject", &subjects).Error
|
||||
return subjects, err
|
||||
}
|
||||
|
||||
func (r *AssignmentRepository) SearchWithPreload(userID uint, queryStr, priority, filter string, page, pageSize int) ([]models.Assignment, int64, error) {
|
||||
var assignments []models.Assignment
|
||||
var totalCount int64
|
||||
|
||||
dbQuery := r.db.Model(&models.Assignment{}).Where("user_id = ?", userID)
|
||||
|
||||
if queryStr != "" {
|
||||
dbQuery = dbQuery.Where("title LIKE ? OR description LIKE ?", "%"+queryStr+"%", "%"+queryStr+"%")
|
||||
}
|
||||
|
||||
if priority != "" {
|
||||
dbQuery = dbQuery.Where("priority = ?", priority)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
startOfDay := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||||
endOfDay := startOfDay.AddDate(0, 0, 1)
|
||||
weekLater := startOfDay.AddDate(0, 0, 7)
|
||||
|
||||
switch filter {
|
||||
case "completed":
|
||||
dbQuery = dbQuery.Where("is_completed = ?", true)
|
||||
case "overdue":
|
||||
dbQuery = dbQuery.Where("is_completed = ? AND due_date < ?", false, now)
|
||||
case "due_today":
|
||||
dbQuery = dbQuery.Where("is_completed = ? AND due_date >= ? AND due_date < ?", false, startOfDay, endOfDay)
|
||||
case "due_this_week":
|
||||
dbQuery = dbQuery.Where("is_completed = ? AND due_date >= ? AND due_date < ?", false, startOfDay, weekLater)
|
||||
case "recurring":
|
||||
dbQuery = dbQuery.Where("recurring_assignment_id IS NOT NULL")
|
||||
default:
|
||||
dbQuery = dbQuery.Where("is_completed = ?", false)
|
||||
}
|
||||
|
||||
if err := dbQuery.Count(&totalCount).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
if filter == "completed" {
|
||||
dbQuery = dbQuery.Order("completed_at DESC")
|
||||
} else {
|
||||
dbQuery = dbQuery.Order("due_date ASC")
|
||||
}
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 10
|
||||
}
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
err := dbQuery.Preload("RecurringAssignment").Limit(pageSize).Offset(offset).Find(&assignments).Error
|
||||
return assignments, totalCount, err
|
||||
}
|
||||
|
||||
@@ -48,6 +48,19 @@ func getFuncMap() template.FuncMap {
|
||||
"recurringLabel": service.GetRecurrenceTypeLabel,
|
||||
"endTypeLabel": service.GetEndTypeLabel,
|
||||
"recurringSummary": service.FormatRecurringSummary,
|
||||
"derefInt": func(i *int) int {
|
||||
if i == nil {
|
||||
return 0
|
||||
}
|
||||
return *i
|
||||
},
|
||||
"seq": func(start, end int) []int {
|
||||
var result []int
|
||||
for i := start; i <= end; i++ {
|
||||
result = append(result, i)
|
||||
}
|
||||
return result
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +80,7 @@ func loadTemplates() (*template.Template, error) {
|
||||
{"web/templates/auth/*.html", ""},
|
||||
{"web/templates/pages/*.html", ""},
|
||||
{"web/templates/assignments/*.html", "assignments/"},
|
||||
{"web/templates/recurring/*.html", "recurring/"},
|
||||
{"web/templates/admin/*.html", "admin/"},
|
||||
}
|
||||
|
||||
@@ -187,6 +201,7 @@ func Setup(cfg *config.Config) *gin.Engine {
|
||||
adminHandler := handler.NewAdminHandler()
|
||||
profileHandler := handler.NewProfileHandler(notificationService)
|
||||
apiHandler := handler.NewAPIHandler()
|
||||
apiRecurringHandler := handler.NewAPIRecurringHandler()
|
||||
|
||||
guest := r.Group("/")
|
||||
guest.Use(middleware.GuestOnly())
|
||||
@@ -226,6 +241,13 @@ func Setup(cfg *config.Config) *gin.Engine {
|
||||
auth.POST("/statistics/archive-subject", assignmentHandler.ArchiveSubject)
|
||||
auth.POST("/statistics/unarchive-subject", assignmentHandler.UnarchiveSubject)
|
||||
|
||||
auth.POST("/recurring/:id/stop", assignmentHandler.StopRecurring)
|
||||
auth.POST("/recurring/:id/resume", assignmentHandler.ResumeRecurring)
|
||||
auth.POST("/recurring/:id/delete", assignmentHandler.DeleteRecurring)
|
||||
auth.GET("/recurring", assignmentHandler.ListRecurring)
|
||||
auth.GET("/recurring/:id/edit", assignmentHandler.EditRecurring)
|
||||
auth.POST("/recurring/:id", assignmentHandler.UpdateRecurring)
|
||||
|
||||
auth.GET("/profile", profileHandler.Show)
|
||||
auth.POST("/profile", profileHandler.Update)
|
||||
auth.POST("/profile/password", profileHandler.ChangePassword)
|
||||
@@ -258,7 +280,13 @@ func Setup(cfg *config.Config) *gin.Engine {
|
||||
api.PUT("/assignments/:id", apiHandler.UpdateAssignment)
|
||||
api.DELETE("/assignments/:id", apiHandler.DeleteAssignment)
|
||||
api.PATCH("/assignments/:id/toggle", apiHandler.ToggleAssignment)
|
||||
|
||||
api.GET("/statistics", apiHandler.GetStatistics)
|
||||
|
||||
api.GET("/recurring", apiRecurringHandler.ListRecurring)
|
||||
api.GET("/recurring/:id", apiRecurringHandler.GetRecurring)
|
||||
api.PUT("/recurring/:id", apiRecurringHandler.UpdateRecurring)
|
||||
api.DELETE("/recurring/:id", apiRecurringHandler.DeleteRecurring)
|
||||
}
|
||||
|
||||
return r
|
||||
|
||||
@@ -179,7 +179,7 @@ func (s *AssignmentService) SearchAssignments(userID uint, query, priority, filt
|
||||
pageSize = 10
|
||||
}
|
||||
|
||||
assignments, totalCount, err := s.assignmentRepo.Search(userID, query, priority, filter, page, pageSize)
|
||||
assignments, totalCount, err := s.assignmentRepo.SearchWithPreload(userID, query, priority, filter, page, pageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -255,11 +255,11 @@ func (s *AssignmentService) GetSubjectsByUser(userID uint) ([]string, error) {
|
||||
}
|
||||
|
||||
type DashboardStats struct {
|
||||
TotalPending int64
|
||||
DueToday int
|
||||
DueThisWeek int
|
||||
Overdue int
|
||||
Subjects []string
|
||||
TotalPending int64
|
||||
DueToday int
|
||||
DueThisWeek int
|
||||
Overdue int
|
||||
Subjects []string
|
||||
}
|
||||
|
||||
func (s *AssignmentService) GetDashboardStats(userID uint) (*DashboardStats, error) {
|
||||
@@ -270,11 +270,11 @@ func (s *AssignmentService) GetDashboardStats(userID uint) (*DashboardStats, err
|
||||
subjects, _ := s.assignmentRepo.GetSubjectsByUserID(userID)
|
||||
|
||||
return &DashboardStats{
|
||||
TotalPending: pending,
|
||||
DueToday: len(dueToday),
|
||||
DueThisWeek: len(dueThisWeek),
|
||||
Overdue: int(overdueCount),
|
||||
Subjects: subjects,
|
||||
TotalPending: pending,
|
||||
DueToday: len(dueToday),
|
||||
DueThisWeek: len(dueThisWeek),
|
||||
Overdue: int(overdueCount),
|
||||
Subjects: subjects,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -392,4 +392,3 @@ func (s *AssignmentService) GetSubjectsWithArchived(userID uint, includeArchived
|
||||
func (s *AssignmentService) GetArchivedSubjects(userID uint) ([]string, error) {
|
||||
return s.assignmentRepo.GetArchivedSubjects(userID)
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ func NewRecurringAssignmentService() *RecurringAssignmentService {
|
||||
}
|
||||
}
|
||||
|
||||
type CreateRecurringInput struct {
|
||||
type CreateRecurringAssignmentInput struct {
|
||||
Title string
|
||||
Description string
|
||||
Subject string
|
||||
@@ -50,7 +50,7 @@ type CreateRecurringInput struct {
|
||||
FirstDueDate time.Time
|
||||
}
|
||||
|
||||
func (s *RecurringAssignmentService) Create(userID uint, input CreateRecurringInput) (*models.RecurringAssignment, error) {
|
||||
func (s *RecurringAssignmentService) Create(userID uint, input CreateRecurringAssignmentInput) (*models.RecurringAssignment, error) {
|
||||
if !isValidRecurrenceType(input.RecurrenceType) {
|
||||
return nil, ErrInvalidRecurrenceType
|
||||
}
|
||||
@@ -121,15 +121,22 @@ func (s *RecurringAssignmentService) GetActiveByUser(userID uint) ([]models.Recu
|
||||
}
|
||||
|
||||
type UpdateRecurringInput struct {
|
||||
Title string
|
||||
Description string
|
||||
Subject string
|
||||
Priority string
|
||||
DueTime string
|
||||
Title *string
|
||||
Description *string
|
||||
Subject *string
|
||||
Priority *string
|
||||
RecurrenceType *string
|
||||
RecurrenceInterval *int
|
||||
RecurrenceWeekday *int
|
||||
RecurrenceDay *int
|
||||
DueTime *string
|
||||
EndType *string
|
||||
EndCount *int
|
||||
EndDate *time.Time
|
||||
EditBehavior string
|
||||
ReminderEnabled bool
|
||||
ReminderEnabled *bool
|
||||
ReminderOffset *int
|
||||
UrgentReminderEnabled bool
|
||||
UrgentReminderEnabled *bool
|
||||
}
|
||||
|
||||
func (s *RecurringAssignmentService) Update(userID, recurringID uint, input UpdateRecurringInput) (*models.RecurringAssignment, error) {
|
||||
@@ -138,19 +145,56 @@ func (s *RecurringAssignmentService) Update(userID, recurringID uint, input Upda
|
||||
return nil, err
|
||||
}
|
||||
|
||||
recurring.Title = input.Title
|
||||
recurring.Description = input.Description
|
||||
recurring.Subject = input.Subject
|
||||
recurring.Priority = input.Priority
|
||||
if input.DueTime != "" {
|
||||
recurring.DueTime = input.DueTime
|
||||
if input.Title != nil {
|
||||
recurring.Title = *input.Title
|
||||
}
|
||||
if input.Description != nil {
|
||||
recurring.Description = *input.Description
|
||||
}
|
||||
if input.Subject != nil {
|
||||
recurring.Subject = *input.Subject
|
||||
}
|
||||
if input.Priority != nil {
|
||||
recurring.Priority = *input.Priority
|
||||
}
|
||||
if input.DueTime != nil {
|
||||
recurring.DueTime = *input.DueTime
|
||||
}
|
||||
if input.EditBehavior != "" {
|
||||
recurring.EditBehavior = input.EditBehavior
|
||||
}
|
||||
recurring.ReminderEnabled = input.ReminderEnabled
|
||||
recurring.ReminderOffset = input.ReminderOffset
|
||||
recurring.UrgentReminderEnabled = input.UrgentReminderEnabled
|
||||
if input.ReminderEnabled != nil {
|
||||
recurring.ReminderEnabled = *input.ReminderEnabled
|
||||
}
|
||||
if input.ReminderOffset != nil {
|
||||
recurring.ReminderOffset = input.ReminderOffset
|
||||
}
|
||||
if input.UrgentReminderEnabled != nil {
|
||||
recurring.UrgentReminderEnabled = *input.UrgentReminderEnabled
|
||||
}
|
||||
|
||||
if input.RecurrenceType != nil && *input.RecurrenceType != "" && isValidRecurrenceType(*input.RecurrenceType) {
|
||||
recurring.RecurrenceType = *input.RecurrenceType
|
||||
}
|
||||
if input.RecurrenceInterval != nil && *input.RecurrenceInterval > 0 {
|
||||
recurring.RecurrenceInterval = *input.RecurrenceInterval
|
||||
}
|
||||
if input.RecurrenceWeekday != nil {
|
||||
recurring.RecurrenceWeekday = input.RecurrenceWeekday
|
||||
}
|
||||
if input.RecurrenceDay != nil {
|
||||
recurring.RecurrenceDay = input.RecurrenceDay
|
||||
}
|
||||
|
||||
if input.EndType != nil && isValidEndType(*input.EndType) {
|
||||
recurring.EndType = *input.EndType
|
||||
}
|
||||
if input.EndCount != nil {
|
||||
recurring.EndCount = input.EndCount
|
||||
}
|
||||
if input.EndDate != nil {
|
||||
recurring.EndDate = input.EndDate
|
||||
}
|
||||
|
||||
if err := s.recurringRepo.Update(recurring); err != nil {
|
||||
return nil, err
|
||||
@@ -159,6 +203,16 @@ func (s *RecurringAssignmentService) Update(userID, recurringID uint, input Upda
|
||||
return recurring, nil
|
||||
}
|
||||
|
||||
func (s *RecurringAssignmentService) SetActive(userID, recurringID uint, isActive bool) error {
|
||||
recurring, err := s.GetByID(userID, recurringID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
recurring.IsActive = isActive
|
||||
return s.recurringRepo.Update(recurring)
|
||||
}
|
||||
|
||||
func (s *RecurringAssignmentService) UpdateAssignmentWithBehavior(
|
||||
userID uint,
|
||||
assignment *models.Assignment,
|
||||
|
||||
Reference in New Issue
Block a user