399 lines
12 KiB
Go
399 lines
12 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
|
|
}
|
|
|
|
func NewAPIHandler() *APIHandler {
|
|
return &APIHandler{
|
|
assignmentService: service.NewAssignmentService(),
|
|
}
|
|
}
|
|
|
|
func (h *APIHandler) getUserID(c *gin.Context) uint {
|
|
userID, _ := c.Get(middleware.UserIDKey)
|
|
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
|
|
}
|
|
if pageSize < 1 {
|
|
pageSize = 20
|
|
}
|
|
if pageSize > 100 {
|
|
pageSize = 100 // Maximum page size to prevent abuse
|
|
}
|
|
|
|
// Use paginated methods for filtered queries
|
|
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:
|
|
// 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
|
|
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,
|
|
})
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
|
|
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)
|
|
}
|
|
|
|
// 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)
|
|
|
|
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)
|
|
}
|
|
|
|
// 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)
|
|
|
|
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)
|
|
}
|
|
|
|
// ListDueTodayAssignments returns assignments due today
|
|
// GET /api/v1/assignments/due-today
|
|
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),
|
|
})
|
|
}
|
|
|
|
// 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)
|
|
|
|
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),
|
|
})
|
|
}
|
|
|
|
// 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"))
|
|
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
if pageSize < 1 {
|
|
pageSize = 20
|
|
}
|
|
if pageSize > 100 {
|
|
pageSize = 100
|
|
}
|
|
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,
|
|
"count": len(result.Assignments),
|
|
"total_count": result.TotalCount,
|
|
"total_pages": result.TotalPages,
|
|
"current_page": result.CurrentPage,
|
|
"page_size": result.PageSize,
|
|
})
|
|
}
|
|
|
|
// 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)
|
|
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)
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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"})
|
|
return
|
|
}
|
|
|
|
dueDate, err := time.Parse(time.RFC3339, 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)
|
|
}
|
|
}
|
|
|
|
assignment, err := h.assignmentService.Create(userID, input.Title, input.Description, input.Subject, input.Priority, dueDate)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create assignment"})
|
|
return
|
|
}
|
|
|
|
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"`
|
|
}
|
|
|
|
// 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)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid assignment ID"})
|
|
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"})
|
|
return
|
|
}
|
|
|
|
var input UpdateAssignmentInput
|
|
if err := c.ShouldBindJSON(&input); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input"})
|
|
return
|
|
}
|
|
|
|
// Use existing values if not provided
|
|
title := input.Title
|
|
if title == "" {
|
|
title = existing.Title
|
|
}
|
|
|
|
description := input.Description
|
|
subject := input.Subject
|
|
priority := input.Priority
|
|
if priority == "" {
|
|
priority = existing.Priority
|
|
}
|
|
|
|
dueDate := existing.DueDate
|
|
if input.DueDate != "" {
|
|
dueDate, err = time.Parse(time.RFC3339, 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
|
|
}
|
|
}
|
|
}
|
|
|
|
assignment, err := h.assignmentService.Update(userID, uint(id), title, description, subject, priority, dueDate)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update assignment"})
|
|
return
|
|
}
|
|
|
|
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)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid assignment ID"})
|
|
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"})
|
|
}
|
|
|
|
// 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)
|
|
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)
|
|
}
|