first commit
This commit is contained in:
165
internal/handler/admin_handler.go
Normal file
165
internal/handler/admin_handler.go
Normal file
@@ -0,0 +1,165 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"homework-manager/internal/middleware"
|
||||
"homework-manager/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AdminHandler struct {
|
||||
adminService *service.AdminService
|
||||
apiKeyService *service.APIKeyService
|
||||
}
|
||||
|
||||
func NewAdminHandler() *AdminHandler {
|
||||
return &AdminHandler{
|
||||
adminService: service.NewAdminService(),
|
||||
apiKeyService: service.NewAPIKeyService(),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AdminHandler) getUserID(c *gin.Context) uint {
|
||||
userID, _ := c.Get(middleware.UserIDKey)
|
||||
return userID.(uint)
|
||||
}
|
||||
|
||||
func (h *AdminHandler) Index(c *gin.Context) {
|
||||
users, _ := h.adminService.GetAllUsers()
|
||||
currentUserID := h.getUserID(c)
|
||||
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
|
||||
RenderHTML(c, http.StatusOK, "admin/users.html", gin.H{
|
||||
"title": "ユーザー管理",
|
||||
"users": users,
|
||||
"currentUserID": currentUserID,
|
||||
"isAdmin": true,
|
||||
"userName": name,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AdminHandler) DeleteUser(c *gin.Context) {
|
||||
adminID := h.getUserID(c)
|
||||
targetID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "無効なユーザーID"})
|
||||
return
|
||||
}
|
||||
|
||||
err = h.adminService.DeleteUser(adminID, uint(targetID))
|
||||
if err != nil {
|
||||
users, _ := h.adminService.GetAllUsers()
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
|
||||
RenderHTML(c, http.StatusOK, "admin/users.html", gin.H{
|
||||
"title": "ユーザー管理",
|
||||
"users": users,
|
||||
"currentUserID": adminID,
|
||||
"error": err.Error(),
|
||||
"isAdmin": true,
|
||||
"userName": name,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.Redirect(http.StatusFound, "/admin/users")
|
||||
}
|
||||
|
||||
func (h *AdminHandler) ChangeRole(c *gin.Context) {
|
||||
adminID := h.getUserID(c)
|
||||
targetID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "無効なユーザーID"})
|
||||
return
|
||||
}
|
||||
newRole := c.PostForm("role")
|
||||
|
||||
err = h.adminService.ChangeRole(adminID, uint(targetID), newRole)
|
||||
if err != nil {
|
||||
users, _ := h.adminService.GetAllUsers()
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
|
||||
RenderHTML(c, http.StatusOK, "admin/users.html", gin.H{
|
||||
"title": "ユーザー管理",
|
||||
"users": users,
|
||||
"currentUserID": adminID,
|
||||
"error": err.Error(),
|
||||
"isAdmin": true,
|
||||
"userName": name,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.Redirect(http.StatusFound, "/admin/users")
|
||||
}
|
||||
|
||||
func (h *AdminHandler) APIKeys(c *gin.Context) {
|
||||
keys, _ := h.apiKeyService.GetAllAPIKeys()
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
|
||||
RenderHTML(c, http.StatusOK, "admin/api_keys.html", gin.H{
|
||||
"title": "APIキー管理",
|
||||
"apiKeys": keys,
|
||||
"isAdmin": true,
|
||||
"userName": name,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AdminHandler) CreateAPIKey(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
keyName := c.PostForm("name")
|
||||
|
||||
plainKey, _, err := h.apiKeyService.CreateAPIKey(userID, keyName)
|
||||
keys, _ := h.apiKeyService.GetAllAPIKeys()
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
|
||||
if err != nil {
|
||||
RenderHTML(c, http.StatusOK, "admin/api_keys.html", gin.H{
|
||||
"title": "APIキー管理",
|
||||
"apiKeys": keys,
|
||||
"error": err.Error(),
|
||||
"isAdmin": true,
|
||||
"userName": name,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
RenderHTML(c, http.StatusOK, "admin/api_keys.html", gin.H{
|
||||
"title": "APIキー管理",
|
||||
"apiKeys": keys,
|
||||
"newKey": plainKey,
|
||||
"newKeyName": keyName,
|
||||
"isAdmin": true,
|
||||
"userName": name,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AdminHandler) DeleteAPIKey(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "無効なAPIキーID"})
|
||||
return
|
||||
}
|
||||
|
||||
err = h.apiKeyService.DeleteAPIKey(uint(id))
|
||||
if err != nil {
|
||||
keys, _ := h.apiKeyService.GetAllAPIKeys()
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
|
||||
RenderHTML(c, http.StatusOK, "admin/api_keys.html", gin.H{
|
||||
"title": "APIキー管理",
|
||||
"apiKeys": keys,
|
||||
"error": err.Error(),
|
||||
"isAdmin": true,
|
||||
"userName": name,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.Redirect(http.StatusFound, "/admin/api-keys")
|
||||
}
|
||||
|
||||
398
internal/handler/api_handler.go
Normal file
398
internal/handler/api_handler.go
Normal file
@@ -0,0 +1,398 @@
|
||||
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)
|
||||
}
|
||||
233
internal/handler/assignment_handler.go
Normal file
233
internal/handler/assignment_handler.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"homework-manager/internal/middleware"
|
||||
"homework-manager/internal/models"
|
||||
"homework-manager/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AssignmentHandler struct {
|
||||
assignmentService *service.AssignmentService
|
||||
}
|
||||
|
||||
func NewAssignmentHandler() *AssignmentHandler {
|
||||
return &AssignmentHandler{
|
||||
assignmentService: service.NewAssignmentService(),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AssignmentHandler) getUserID(c *gin.Context) uint {
|
||||
userID, _ := c.Get(middleware.UserIDKey)
|
||||
return userID.(uint)
|
||||
}
|
||||
|
||||
func (h *AssignmentHandler) Dashboard(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
stats, _ := h.assignmentService.GetDashboardStats(userID)
|
||||
dueToday, _ := h.assignmentService.GetDueTodayByUser(userID)
|
||||
overdue, _ := h.assignmentService.GetOverdueByUser(userID)
|
||||
upcoming, _ := h.assignmentService.GetDueThisWeekByUser(userID)
|
||||
|
||||
role, _ := c.Get(middleware.UserRoleKey)
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
|
||||
RenderHTML(c, http.StatusOK, "dashboard.html", gin.H{
|
||||
"title": "ダッシュボード",
|
||||
"stats": stats,
|
||||
"dueToday": dueToday,
|
||||
"overdue": overdue,
|
||||
"upcoming": upcoming,
|
||||
"isAdmin": role == "admin",
|
||||
"userName": name,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AssignmentHandler) Index(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
filter := c.Query("filter")
|
||||
filter = strings.TrimSpace(filter)
|
||||
if filter == "" {
|
||||
filter = "pending"
|
||||
}
|
||||
query := c.Query("q")
|
||||
priority := c.Query("priority")
|
||||
pageStr := c.DefaultQuery("page", "1")
|
||||
page, err := strconv.Atoi(pageStr)
|
||||
if err != nil || page < 1 {
|
||||
page = 1
|
||||
}
|
||||
const pageSize = 10
|
||||
|
||||
result, err := h.assignmentService.SearchAssignments(userID, query, priority, filter, page, pageSize)
|
||||
|
||||
var assignments []models.Assignment
|
||||
var totalPages, currentPage int
|
||||
if err != nil || result == nil {
|
||||
assignments = []models.Assignment{}
|
||||
totalPages = 1
|
||||
currentPage = 1
|
||||
} else {
|
||||
assignments = result.Assignments
|
||||
totalPages = result.TotalPages
|
||||
currentPage = result.CurrentPage
|
||||
}
|
||||
|
||||
role, _ := c.Get(middleware.UserRoleKey)
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
|
||||
RenderHTML(c, http.StatusOK, "assignments/index.html", gin.H{
|
||||
"title": "課題一覧",
|
||||
"assignments": assignments,
|
||||
"filter": filter,
|
||||
"query": query,
|
||||
"priority": priority,
|
||||
"isAdmin": role == "admin",
|
||||
"userName": name,
|
||||
"currentPage": currentPage,
|
||||
"totalPages": totalPages,
|
||||
"hasPrev": currentPage > 1,
|
||||
"hasNext": currentPage < totalPages,
|
||||
"prevPage": currentPage - 1,
|
||||
"nextPage": currentPage + 1,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AssignmentHandler) New(c *gin.Context) {
|
||||
role, _ := c.Get(middleware.UserRoleKey)
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
|
||||
RenderHTML(c, http.StatusOK, "assignments/new.html", gin.H{
|
||||
"title": "課題登録",
|
||||
"isAdmin": role == "admin",
|
||||
"userName": name,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AssignmentHandler) Create(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
|
||||
title := c.PostForm("title")
|
||||
description := c.PostForm("description")
|
||||
subject := c.PostForm("subject")
|
||||
priority := c.PostForm("priority")
|
||||
dueDateStr := c.PostForm("due_date")
|
||||
|
||||
dueDate, err := time.ParseInLocation("2006-01-02T15:04", dueDateStr, time.Local)
|
||||
if err != nil {
|
||||
dueDate, err = time.ParseInLocation("2006-01-02", dueDateStr, time.Local)
|
||||
if err != nil {
|
||||
role, _ := c.Get(middleware.UserRoleKey)
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
RenderHTML(c, http.StatusOK, "assignments/new.html", gin.H{
|
||||
"title": "課題登録",
|
||||
"error": "提出期限の形式が正しくありません",
|
||||
"formTitle": title,
|
||||
"description": description,
|
||||
"subject": subject,
|
||||
"priority": priority,
|
||||
"isAdmin": role == "admin",
|
||||
"userName": name,
|
||||
})
|
||||
return
|
||||
}
|
||||
dueDate = dueDate.Add(23*time.Hour + 59*time.Minute)
|
||||
}
|
||||
|
||||
_, err = h.assignmentService.Create(userID, title, description, subject, priority, dueDate)
|
||||
if err != nil {
|
||||
role, _ := c.Get(middleware.UserRoleKey)
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
RenderHTML(c, http.StatusOK, "assignments/new.html", gin.H{
|
||||
"title": "課題登録",
|
||||
"error": "課題の登録に失敗しました",
|
||||
"formTitle": title,
|
||||
"description": description,
|
||||
"subject": subject,
|
||||
"priority": priority,
|
||||
"isAdmin": role == "admin",
|
||||
"userName": name,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.Redirect(http.StatusFound, "/assignments")
|
||||
}
|
||||
|
||||
func (h *AssignmentHandler) Edit(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
|
||||
assignment, err := h.assignmentService.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, "assignments/edit.html", gin.H{
|
||||
"title": "課題編集",
|
||||
"assignment": assignment,
|
||||
"isAdmin": role == "admin",
|
||||
"userName": name,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AssignmentHandler) Update(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
|
||||
title := c.PostForm("title")
|
||||
description := c.PostForm("description")
|
||||
subject := c.PostForm("subject")
|
||||
priority := c.PostForm("priority")
|
||||
dueDateStr := c.PostForm("due_date")
|
||||
|
||||
dueDate, err := time.ParseInLocation("2006-01-02T15:04", dueDateStr, time.Local)
|
||||
if err != nil {
|
||||
dueDate, err = time.ParseInLocation("2006-01-02", dueDateStr, time.Local)
|
||||
if err != nil {
|
||||
c.Redirect(http.StatusFound, "/assignments")
|
||||
return
|
||||
}
|
||||
dueDate = dueDate.Add(23*time.Hour + 59*time.Minute)
|
||||
}
|
||||
|
||||
_, err = h.assignmentService.Update(userID, uint(id), title, description, subject, priority, dueDate)
|
||||
if err != nil {
|
||||
c.Redirect(http.StatusFound, "/assignments")
|
||||
return
|
||||
}
|
||||
|
||||
c.Redirect(http.StatusFound, "/assignments")
|
||||
}
|
||||
|
||||
func (h *AssignmentHandler) Toggle(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
|
||||
h.assignmentService.ToggleComplete(userID, uint(id))
|
||||
|
||||
referer := c.Request.Referer()
|
||||
if referer == "" {
|
||||
referer = "/assignments"
|
||||
}
|
||||
c.Redirect(http.StatusFound, referer)
|
||||
}
|
||||
|
||||
func (h *AssignmentHandler) Delete(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
id, _ := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
|
||||
h.assignmentService.Delete(userID, uint(id))
|
||||
|
||||
c.Redirect(http.StatusFound, "/assignments")
|
||||
}
|
||||
114
internal/handler/auth_handler.go
Normal file
114
internal/handler/auth_handler.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"homework-manager/internal/middleware"
|
||||
"homework-manager/internal/service"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type AuthHandler struct {
|
||||
authService *service.AuthService
|
||||
}
|
||||
|
||||
func NewAuthHandler() *AuthHandler {
|
||||
return &AuthHandler{
|
||||
authService: service.NewAuthService(),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *AuthHandler) ShowLogin(c *gin.Context) {
|
||||
RenderHTML(c, http.StatusOK, "login.html", gin.H{
|
||||
"title": "ログイン",
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Login(c *gin.Context) {
|
||||
email := c.PostForm("email")
|
||||
password := c.PostForm("password")
|
||||
|
||||
user, err := h.authService.Login(email, password)
|
||||
if err != nil {
|
||||
RenderHTML(c, http.StatusOK, "login.html", gin.H{
|
||||
"title": "ログイン",
|
||||
"error": "メールアドレスまたはパスワードが正しくありません",
|
||||
"email": email,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
session := sessions.Default(c)
|
||||
session.Set(middleware.UserIDKey, user.ID)
|
||||
session.Set(middleware.UserRoleKey, user.Role)
|
||||
session.Set(middleware.UserNameKey, user.Name)
|
||||
session.Save()
|
||||
|
||||
c.Redirect(http.StatusFound, "/")
|
||||
}
|
||||
|
||||
func (h *AuthHandler) ShowRegister(c *gin.Context) {
|
||||
RenderHTML(c, http.StatusOK, "register.html", gin.H{
|
||||
"title": "新規登録",
|
||||
})
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Register(c *gin.Context) {
|
||||
email := c.PostForm("email")
|
||||
password := c.PostForm("password")
|
||||
passwordConfirm := c.PostForm("password_confirm")
|
||||
name := c.PostForm("name")
|
||||
|
||||
if password != passwordConfirm {
|
||||
RenderHTML(c, http.StatusOK, "register.html", gin.H{
|
||||
"title": "新規登録",
|
||||
"error": "パスワードが一致しません",
|
||||
"email": email,
|
||||
"name": name,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if len(password) < 8 {
|
||||
RenderHTML(c, http.StatusOK, "register.html", gin.H{
|
||||
"title": "新規登録",
|
||||
"error": "パスワードは8文字以上で入力してください",
|
||||
"email": email,
|
||||
"name": name,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.authService.Register(email, password, name)
|
||||
if err != nil {
|
||||
errorMsg := "登録に失敗しました"
|
||||
if err == service.ErrEmailAlreadyExists {
|
||||
errorMsg = "このメールアドレスは既に使用されています"
|
||||
}
|
||||
RenderHTML(c, http.StatusOK, "register.html", gin.H{
|
||||
"title": "新規登録",
|
||||
"error": errorMsg,
|
||||
"email": email,
|
||||
"name": name,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
session := sessions.Default(c)
|
||||
session.Set(middleware.UserIDKey, user.ID)
|
||||
session.Set(middleware.UserRoleKey, user.Role)
|
||||
session.Set(middleware.UserNameKey, user.Name)
|
||||
session.Save()
|
||||
|
||||
c.Redirect(http.StatusFound, "/")
|
||||
}
|
||||
|
||||
func (h *AuthHandler) Logout(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
session.Clear()
|
||||
session.Save()
|
||||
|
||||
c.Redirect(http.StatusFound, "/login")
|
||||
}
|
||||
33
internal/handler/helper.go
Normal file
33
internal/handler/helper.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const csrfTokenKey = "csrf_token"
|
||||
const csrfTokenFormKey = "_csrf"
|
||||
|
||||
func RenderHTML(c *gin.Context, code int, name string, obj gin.H) {
|
||||
if obj == nil {
|
||||
obj = gin.H{}
|
||||
}
|
||||
|
||||
if startTime, exists := c.Get("startTime"); exists {
|
||||
duration := time.Since(startTime.(time.Time))
|
||||
obj["processing_time"] = fmt.Sprintf("%.2fms", float64(duration.Microseconds())/1000.0)
|
||||
} else {
|
||||
obj["processing_time"] = "unknown"
|
||||
}
|
||||
|
||||
if token, exists := c.Get(csrfTokenKey); exists {
|
||||
obj["csrfToken"] = token.(string)
|
||||
obj["csrfField"] = template.HTML(`<input type="hidden" name="` + csrfTokenFormKey + `" value="` + token.(string) + `">`)
|
||||
}
|
||||
|
||||
c.HTML(code, name, obj)
|
||||
}
|
||||
|
||||
122
internal/handler/profile_handler.go
Normal file
122
internal/handler/profile_handler.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"homework-manager/internal/middleware"
|
||||
"homework-manager/internal/service"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type ProfileHandler struct {
|
||||
authService *service.AuthService
|
||||
}
|
||||
|
||||
func NewProfileHandler() *ProfileHandler {
|
||||
return &ProfileHandler{
|
||||
authService: service.NewAuthService(),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ProfileHandler) getUserID(c *gin.Context) uint {
|
||||
userID, _ := c.Get(middleware.UserIDKey)
|
||||
return userID.(uint)
|
||||
}
|
||||
|
||||
func (h *ProfileHandler) Show(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
user, _ := h.authService.GetUserByID(userID)
|
||||
|
||||
role, _ := c.Get(middleware.UserRoleKey)
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
|
||||
RenderHTML(c, http.StatusOK, "profile.html", gin.H{
|
||||
"title": "プロフィール",
|
||||
"user": user,
|
||||
"isAdmin": role == "admin",
|
||||
"userName": name,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ProfileHandler) Update(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
name := c.PostForm("name")
|
||||
|
||||
err := h.authService.UpdateProfile(userID, name)
|
||||
|
||||
role, _ := c.Get(middleware.UserRoleKey)
|
||||
user, _ := h.authService.GetUserByID(userID)
|
||||
|
||||
if err != nil {
|
||||
RenderHTML(c, http.StatusOK, "profile.html", gin.H{
|
||||
"title": "プロフィール",
|
||||
"user": user,
|
||||
"error": "プロフィールの更新に失敗しました",
|
||||
"isAdmin": role == "admin",
|
||||
"userName": name,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
RenderHTML(c, http.StatusOK, "profile.html", gin.H{
|
||||
"title": "プロフィール",
|
||||
"user": user,
|
||||
"success": "プロフィールを更新しました",
|
||||
"isAdmin": role == "admin",
|
||||
"userName": user.Name,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *ProfileHandler) ChangePassword(c *gin.Context) {
|
||||
userID := h.getUserID(c)
|
||||
oldPassword := c.PostForm("old_password")
|
||||
newPassword := c.PostForm("new_password")
|
||||
confirmPassword := c.PostForm("confirm_password")
|
||||
|
||||
role, _ := c.Get(middleware.UserRoleKey)
|
||||
name, _ := c.Get(middleware.UserNameKey)
|
||||
user, _ := h.authService.GetUserByID(userID)
|
||||
|
||||
if newPassword != confirmPassword {
|
||||
RenderHTML(c, http.StatusOK, "profile.html", gin.H{
|
||||
"title": "プロフィール",
|
||||
"user": user,
|
||||
"passwordError": "新しいパスワードが一致しません",
|
||||
"isAdmin": role == "admin",
|
||||
"userName": name,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if len(newPassword) < 8 {
|
||||
RenderHTML(c, http.StatusOK, "profile.html", gin.H{
|
||||
"title": "プロフィール",
|
||||
"user": user,
|
||||
"passwordError": "パスワードは8文字以上で入力してください",
|
||||
"isAdmin": role == "admin",
|
||||
"userName": name,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err := h.authService.ChangePassword(userID, oldPassword, newPassword)
|
||||
if err != nil {
|
||||
RenderHTML(c, http.StatusOK, "profile.html", gin.H{
|
||||
"title": "プロフィール",
|
||||
"user": user,
|
||||
"passwordError": "現在のパスワードが正しくありません",
|
||||
"isAdmin": role == "admin",
|
||||
"userName": name,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
RenderHTML(c, http.StatusOK, "profile.html", gin.H{
|
||||
"title": "プロフィール",
|
||||
"user": user,
|
||||
"passwordSuccess": "パスワードを変更しました",
|
||||
"isAdmin": role == "admin",
|
||||
"userName": name,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user