552 lines
15 KiB
Go
552 lines
15 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"homework-manager/internal/middleware"
|
|
"homework-manager/internal/service"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type APIHandler struct {
|
|
assignmentService *service.AssignmentService
|
|
recurringService *service.RecurringAssignmentService
|
|
}
|
|
|
|
func NewAPIHandler() *APIHandler {
|
|
return &APIHandler{
|
|
assignmentService: service.NewAssignmentService(),
|
|
recurringService: service.NewRecurringAssignmentService(),
|
|
}
|
|
}
|
|
|
|
func (h *APIHandler) getUserID(c *gin.Context) uint {
|
|
userID, _ := c.Get(middleware.UserIDKey)
|
|
return userID.(uint)
|
|
}
|
|
|
|
func (h *APIHandler) ListAssignments(c *gin.Context) {
|
|
userID := h.getUserID(c)
|
|
filter := c.Query("filter") // pending, completed, overdue
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
|
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 {
|
|
pageSize = 20
|
|
}
|
|
if pageSize > 100 {
|
|
pageSize = 100
|
|
}
|
|
|
|
switch filter {
|
|
case "completed":
|
|
result, err := h.assignmentService.GetCompletedByUserPaginated(userID, page, pageSize)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch assignments"})
|
|
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,
|
|
})
|
|
return
|
|
case "overdue":
|
|
result, err := h.assignmentService.GetOverdueByUserPaginated(userID, page, pageSize)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch assignments"})
|
|
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,
|
|
})
|
|
return
|
|
case "pending":
|
|
result, err := h.assignmentService.GetPendingByUserPaginated(userID, page, pageSize)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch assignments"})
|
|
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,
|
|
})
|
|
return
|
|
default:
|
|
assignments, err := h.assignmentService.GetAllByUser(userID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch assignments"})
|
|
return
|
|
}
|
|
|
|
totalCount := len(assignments)
|
|
totalPages := (totalCount + pageSize - 1) / pageSize
|
|
start := (page - 1) * pageSize
|
|
end := start + pageSize
|
|
if start > totalCount {
|
|
start = totalCount
|
|
}
|
|
if end > totalCount {
|
|
end = totalCount
|
|
}
|
|
|
|
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,
|
|
})
|
|
}
|
|
}
|
|
|
|
func (h *APIHandler) ListPendingAssignments(c *gin.Context) {
|
|
userID := h.getUserID(c)
|
|
page, pageSize := h.parsePagination(c)
|
|
|
|
result, err := h.assignmentService.GetPendingByUserPaginated(userID, page, pageSize)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch assignments"})
|
|
return
|
|
}
|
|
|
|
h.sendPaginatedResponse(c, result)
|
|
}
|
|
|
|
func (h *APIHandler) ListCompletedAssignments(c *gin.Context) {
|
|
userID := h.getUserID(c)
|
|
page, pageSize := h.parsePagination(c)
|
|
|
|
result, err := h.assignmentService.GetCompletedByUserPaginated(userID, page, pageSize)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch assignments"})
|
|
return
|
|
}
|
|
|
|
h.sendPaginatedResponse(c, result)
|
|
}
|
|
|
|
func (h *APIHandler) ListOverdueAssignments(c *gin.Context) {
|
|
userID := h.getUserID(c)
|
|
page, pageSize := h.parsePagination(c)
|
|
|
|
result, err := h.assignmentService.GetOverdueByUserPaginated(userID, page, pageSize)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch assignments"})
|
|
return
|
|
}
|
|
|
|
h.sendPaginatedResponse(c, result)
|
|
}
|
|
|
|
func (h *APIHandler) ListDueTodayAssignments(c *gin.Context) {
|
|
userID := h.getUserID(c)
|
|
|
|
assignments, err := h.assignmentService.GetDueTodayByUser(userID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch assignments"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"assignments": assignments,
|
|
"count": len(assignments),
|
|
})
|
|
}
|
|
|
|
func (h *APIHandler) ListDueThisWeekAssignments(c *gin.Context) {
|
|
userID := h.getUserID(c)
|
|
|
|
assignments, err := h.assignmentService.GetDueThisWeekByUser(userID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch assignments"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"assignments": assignments,
|
|
"count": len(assignments),
|
|
})
|
|
}
|
|
|
|
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"))
|
|
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 {
|
|
pageSize = 20
|
|
}
|
|
if pageSize > 100 {
|
|
pageSize = 100
|
|
}
|
|
return page, pageSize
|
|
}
|
|
|
|
func (h *APIHandler) sendPaginatedResponse(c *gin.Context, result *service.PaginatedResult) {
|
|
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,
|
|
})
|
|
}
|
|
|
|
func (h *APIHandler) GetAssignment(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 assignment ID"})
|
|
return
|
|
}
|
|
|
|
assignment, err := h.assignmentService.GetByID(userID, uint(id))
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Assignment not found"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, assignment)
|
|
}
|
|
|
|
type CreateAssignmentInput struct {
|
|
Title string `json:"title" binding:"required"`
|
|
Description string `json:"description"`
|
|
Subject string `json:"subject"`
|
|
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"`
|
|
}
|
|
|
|
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: " + err.Error()})
|
|
return
|
|
}
|
|
|
|
dueDate, err := parseDateString(input.DueDate)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid due_date format. Use RFC3339 or 2006-01-02T15:04"})
|
|
return
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, 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"`
|
|
ReminderEnabled *bool `json:"reminder_enabled"`
|
|
ReminderAt string `json:"reminder_at"`
|
|
UrgentReminderEnabled *bool `json:"urgent_reminder_enabled"`
|
|
}
|
|
|
|
func (h *APIHandler) UpdateAssignment(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 assignment ID"})
|
|
return
|
|
}
|
|
|
|
existing, err := h.assignmentService.GetByID(userID, uint(id))
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Assignment not found"})
|
|
return
|
|
}
|
|
|
|
var input UpdateAssignmentInput
|
|
if err := c.ShouldBindJSON(&input); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input"})
|
|
return
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
dueDate := existing.DueDate
|
|
if input.DueDate != "" {
|
|
parsedDate, err := parseDateString(input.DueDate)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid due_date format"})
|
|
return
|
|
}
|
|
dueDate = parsedDate
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
c.JSON(http.StatusOK, assignment)
|
|
}
|
|
|
|
func (h *APIHandler) DeleteAssignment(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 assignment ID"})
|
|
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
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Assignment deleted"})
|
|
}
|
|
|
|
func (h *APIHandler) ToggleAssignment(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 assignment ID"})
|
|
return
|
|
}
|
|
|
|
assignment, err := h.assignmentService.ToggleComplete(userID, uint(id))
|
|
if err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Assignment not found"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, assignment)
|
|
}
|
|
|
|
func (h *APIHandler) GetStatistics(c *gin.Context) {
|
|
userID := h.getUserID(c)
|
|
|
|
filter := service.StatisticsFilter{
|
|
Subject: c.Query("subject"),
|
|
IncludeArchived: c.Query("include_archived") == "true",
|
|
}
|
|
|
|
if fromStr := c.Query("from"); fromStr != "" {
|
|
fromDate, err := time.ParseInLocation("2006-01-02", fromStr, time.Local)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid 'from' date format. Use YYYY-MM-DD"})
|
|
return
|
|
}
|
|
filter.From = &fromDate
|
|
}
|
|
|
|
if toStr := c.Query("to"); toStr != "" {
|
|
toDate, err := time.ParseInLocation("2006-01-02", toStr, time.Local)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid 'to' date format. Use YYYY-MM-DD"})
|
|
return
|
|
}
|
|
filter.To = &toDate
|
|
}
|
|
|
|
stats, err := h.assignmentService.GetStatistics(userID, filter)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch statistics"})
|
|
return
|
|
}
|
|
|
|
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
|
|
}
|