Files
Super-HomeworkManager/internal/handler/api_handler.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
}