acd70d319f
- FinanceEntry model (income/expense x fixed/extra) - CRUD API endpoints for finance entries - FinancePage with summary cards and drill-down - Updated navigation with Finance tab - Updated todos default filter to active
262 lines
6.9 KiB
Go
262 lines
6.9 KiB
Go
package handler
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"lifelog/internal/model"
|
|
"lifelog/internal/service"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
type Handler struct {
|
|
todoSvc *service.TodoService
|
|
dailyLogSvc *service.DailyLogService
|
|
financeSvc *service.FinanceService
|
|
}
|
|
|
|
func NewHandler(todoSvc *service.TodoService, dailyLogSvc *service.DailyLogService, financeSvc *service.FinanceService) *Handler {
|
|
return &Handler{todoSvc: todoSvc, dailyLogSvc: dailyLogSvc, financeSvc: financeSvc}
|
|
}
|
|
|
|
// Todo handlers
|
|
|
|
func (h *Handler) GetTodos(c *gin.Context) {
|
|
todos, err := h.todoSvc.GetAll()
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": todos})
|
|
}
|
|
|
|
func (h *Handler) CreateTodo(c *gin.Context) {
|
|
var req struct {
|
|
Title string `json:"title" binding:"required"`
|
|
Priority int `json:"priority"`
|
|
DueDate string `json:"due_date"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if req.Priority == 0 {
|
|
req.Priority = 2
|
|
}
|
|
var dueDate *time.Time
|
|
if req.DueDate != "" {
|
|
t, err := time.Parse("2006-01-02", req.DueDate)
|
|
if err == nil {
|
|
dueDate = &t
|
|
}
|
|
}
|
|
todo, err := h.todoSvc.Create(req.Title, req.Priority, dueDate)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, gin.H{"data": todo})
|
|
}
|
|
|
|
func (h *Handler) UpdateTodo(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
|
return
|
|
}
|
|
var req struct {
|
|
Title string `json:"title" binding:"required"`
|
|
Priority int `json:"priority"`
|
|
Done bool `json:"done"`
|
|
DueDate string `json:"due_date"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
var dueDate *time.Time
|
|
if req.DueDate != "" {
|
|
t, err := time.Parse("2006-01-02", req.DueDate)
|
|
if err == nil {
|
|
dueDate = &t
|
|
}
|
|
}
|
|
todo, err := h.todoSvc.Update(id, req.Title, req.Priority, req.Done, dueDate)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": todo})
|
|
}
|
|
|
|
func (h *Handler) ToggleTodo(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
|
return
|
|
}
|
|
todo, err := h.todoSvc.ToggleDone(id)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": todo})
|
|
}
|
|
|
|
func (h *Handler) DeleteTodo(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
|
return
|
|
}
|
|
err = h.todoSvc.Delete(id)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"message": "deleted"})
|
|
}
|
|
|
|
// DailyLog handlers
|
|
|
|
func (h *Handler) GetDailyLog(c *gin.Context) {
|
|
date := c.Param("date")
|
|
if date == "" {
|
|
date = time.Now().Format("2006-01-02")
|
|
}
|
|
log, err := h.dailyLogSvc.GetByDate(date)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": log})
|
|
}
|
|
|
|
func (h *Handler) GetRecentLogs(c *gin.Context) {
|
|
limitStr := c.DefaultQuery("limit", "30")
|
|
limit, _ := strconv.Atoi(limitStr)
|
|
logs, err := h.dailyLogSvc.GetRecent(limit)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": logs})
|
|
}
|
|
|
|
func (h *Handler) SaveDailyLog(c *gin.Context) {
|
|
date := c.Param("date")
|
|
if date == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "date is required"})
|
|
return
|
|
}
|
|
var req struct {
|
|
Morning string `json:"morning"`
|
|
Afternoon string `json:"afternoon"`
|
|
Evening string `json:"evening"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
log, err := h.dailyLogSvc.Save(date, req.Morning, req.Afternoon, req.Evening)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": log})
|
|
}
|
|
|
|
// Finance handlers
|
|
|
|
func (h *Handler) GetFinanceByYear(c *gin.Context) {
|
|
yearStr := c.Param("year")
|
|
year, err := strconv.Atoi(yearStr)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid year"})
|
|
return
|
|
}
|
|
entries, err := h.financeSvc.GetByYear(year)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"data": entries})
|
|
}
|
|
|
|
func (h *Handler) CreateFinanceEntry(c *gin.Context) {
|
|
var req struct {
|
|
Year int `json:"year" binding:"required"`
|
|
Month int `json:"month"`
|
|
Type string `json:"type" binding:"required"` // income / expense
|
|
Category string `json:"category" binding:"required"` // fixed / extra
|
|
Description string `json:"description"`
|
|
Amount float64 `json:"amount" binding:"required"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
entry := &model.FinanceEntry{
|
|
Year: req.Year,
|
|
Month: req.Month,
|
|
Type: req.Type,
|
|
Category: req.Category,
|
|
Description: req.Description,
|
|
Amount: req.Amount,
|
|
}
|
|
if err := h.financeSvc.Create(entry); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, gin.H{"data": entry})
|
|
}
|
|
|
|
func (h *Handler) UpdateFinanceEntry(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
|
return
|
|
}
|
|
var req struct {
|
|
Year int `json:"year"`
|
|
Month int `json:"month"`
|
|
Type string `json:"type"`
|
|
Category string `json:"category"`
|
|
Description string `json:"description"`
|
|
Amount float64 `json:"amount"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
err = h.financeSvc.Update(&model.FinanceEntry{
|
|
ID: id,
|
|
Year: req.Year,
|
|
Month: req.Month,
|
|
Type: req.Type,
|
|
Category: req.Category,
|
|
Description: req.Description,
|
|
Amount: req.Amount,
|
|
})
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"message": "updated"})
|
|
}
|
|
|
|
func (h *Handler) DeleteFinanceEntry(c *gin.Context) {
|
|
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
|
return
|
|
}
|
|
if err := h.financeSvc.Delete(id); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"message": "deleted"})
|
|
}
|