Files
Super-HomeworkManager/internal/handler/api_handler.go
2025-12-30 21:47:39 +09:00

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)
}