mirror of
https://github.com/Dvorinka/Trackeep.git
synced 2026-06-03 20:12:58 +00:00
first test
This commit is contained in:
@@ -0,0 +1,328 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/trackeep/backend/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// TimeEntryHandler handles time tracking operations
|
||||
type TimeEntryHandler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
// NewTimeEntryHandler creates a new time entry handler
|
||||
func NewTimeEntryHandler(db *gorm.DB) *TimeEntryHandler {
|
||||
return &TimeEntryHandler{db: db}
|
||||
}
|
||||
|
||||
// CreateTimeEntryRequest represents the request to create a time entry
|
||||
type CreateTimeEntryRequest struct {
|
||||
TaskID *uint `json:"task_id,omitempty"`
|
||||
BookmarkID *uint `json:"bookmark_id,omitempty"`
|
||||
NoteID *uint `json:"note_id,omitempty"`
|
||||
Description string `json:"description"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Billable bool `json:"billable"`
|
||||
HourlyRate *float64 `json:"hourly_rate,omitempty"`
|
||||
Source string `json:"source" gorm:"default:manual"`
|
||||
}
|
||||
|
||||
// UpdateTimeEntryRequest represents the request to update a time entry
|
||||
type UpdateTimeEntryRequest struct {
|
||||
Description *string `json:"description,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
Billable *bool `json:"billable,omitempty"`
|
||||
HourlyRate *float64 `json:"hourly_rate,omitempty"`
|
||||
EndTime *time.Time `json:"end_time,omitempty"`
|
||||
}
|
||||
|
||||
// GetTimeEntries retrieves all time entries for the authenticated user
|
||||
func (h *TimeEntryHandler) GetTimeEntries(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var timeEntries []models.TimeEntry
|
||||
query := h.db.Where("user_id = ?", userID).
|
||||
Preload("Task").
|
||||
Preload("Bookmark").
|
||||
Preload("Note").
|
||||
Preload("Tags").
|
||||
Order("created_at DESC")
|
||||
|
||||
// Filter by date range if provided
|
||||
if startDate := c.Query("start_date"); startDate != "" {
|
||||
if parsed, err := time.Parse("2006-01-02", startDate); err == nil {
|
||||
query = query.Where("start_time >= ?", parsed)
|
||||
}
|
||||
}
|
||||
|
||||
if endDate := c.Query("end_date"); endDate != "" {
|
||||
if parsed, err := time.Parse("2006-01-02", endDate); err == nil {
|
||||
query = query.Where("start_time <= ?", parsed.Add(24*time.Hour))
|
||||
}
|
||||
}
|
||||
|
||||
// Filter by running status
|
||||
if isRunning := c.Query("is_running"); isRunning != "" {
|
||||
running := isRunning == "true"
|
||||
query = query.Where("is_running = ?", running)
|
||||
}
|
||||
|
||||
if err := query.Find(&timeEntries).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve time entries"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"time_entries": timeEntries})
|
||||
}
|
||||
|
||||
// GetTimeEntry retrieves a specific time entry
|
||||
func (h *TimeEntryHandler) GetTimeEntry(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid time entry ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var timeEntry models.TimeEntry
|
||||
if err := h.db.Where("id = ? AND user_id = ?", id, userID).
|
||||
Preload("Task").
|
||||
Preload("Bookmark").
|
||||
Preload("Note").
|
||||
Preload("Tags").
|
||||
First(&timeEntry).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Time entry not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve time entry"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"time_entry": timeEntry})
|
||||
}
|
||||
|
||||
// CreateTimeEntry creates a new time entry
|
||||
func (h *TimeEntryHandler) CreateTimeEntry(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var req CreateTimeEntryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
timeEntry := models.TimeEntry{
|
||||
UserID: userID,
|
||||
TaskID: req.TaskID,
|
||||
BookmarkID: req.BookmarkID,
|
||||
NoteID: req.NoteID,
|
||||
Description: req.Description,
|
||||
Billable: req.Billable,
|
||||
HourlyRate: req.HourlyRate,
|
||||
Source: req.Source,
|
||||
StartTime: time.Now(),
|
||||
IsRunning: true,
|
||||
}
|
||||
|
||||
// Handle tags
|
||||
if len(req.Tags) > 0 {
|
||||
var tags []models.Tag
|
||||
for _, tagName := range req.Tags {
|
||||
var tag models.Tag
|
||||
if err := h.db.Where("name = ?", tagName).FirstOrCreate(&tag, models.Tag{Name: tagName}).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create tags"})
|
||||
return
|
||||
}
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
timeEntry.Tags = tags
|
||||
}
|
||||
|
||||
if err := h.db.Create(&timeEntry).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create time entry"})
|
||||
return
|
||||
}
|
||||
|
||||
// Load relationships for response
|
||||
h.db.Preload("Task").Preload("Bookmark").Preload("Note").Preload("Tags").First(&timeEntry)
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"time_entry": timeEntry})
|
||||
}
|
||||
|
||||
// UpdateTimeEntry updates an existing time entry
|
||||
func (h *TimeEntryHandler) UpdateTimeEntry(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid time entry ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var timeEntry models.TimeEntry
|
||||
if err := h.db.Where("id = ? AND user_id = ?", id, userID).First(&timeEntry).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Time entry not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve time entry"})
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateTimeEntryRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Update fields
|
||||
if req.Description != nil {
|
||||
timeEntry.Description = *req.Description
|
||||
}
|
||||
if req.Billable != nil {
|
||||
timeEntry.Billable = *req.Billable
|
||||
}
|
||||
if req.HourlyRate != nil {
|
||||
timeEntry.HourlyRate = req.HourlyRate
|
||||
}
|
||||
if req.EndTime != nil {
|
||||
timeEntry.EndTime = req.EndTime
|
||||
timeEntry.IsRunning = false
|
||||
}
|
||||
|
||||
// Handle tags
|
||||
if req.Tags != nil {
|
||||
// Clear existing tags
|
||||
h.db.Model(&timeEntry).Association("Tags").Clear()
|
||||
|
||||
// Add new tags
|
||||
var tags []models.Tag
|
||||
for _, tagName := range req.Tags {
|
||||
var tag models.Tag
|
||||
if err := h.db.Where("name = ?", tagName).FirstOrCreate(&tag, models.Tag{Name: tagName}).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create tags"})
|
||||
return
|
||||
}
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
timeEntry.Tags = tags
|
||||
}
|
||||
|
||||
if err := h.db.Save(&timeEntry).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update time entry"})
|
||||
return
|
||||
}
|
||||
|
||||
// Load relationships for response
|
||||
h.db.Preload("Task").Preload("Bookmark").Preload("Note").Preload("Tags").First(&timeEntry)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"time_entry": timeEntry})
|
||||
}
|
||||
|
||||
// StopTimeEntry stops a running time entry
|
||||
func (h *TimeEntryHandler) StopTimeEntry(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid time entry ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var timeEntry models.TimeEntry
|
||||
if err := h.db.Where("id = ? AND user_id = ?", id, userID).First(&timeEntry).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Time entry not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve time entry"})
|
||||
return
|
||||
}
|
||||
|
||||
if !timeEntry.IsRunning {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Time entry is already stopped"})
|
||||
return
|
||||
}
|
||||
|
||||
timeEntry.Stop()
|
||||
if err := h.db.Save(&timeEntry).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to stop time entry"})
|
||||
return
|
||||
}
|
||||
|
||||
// Load relationships for response
|
||||
h.db.Preload("Task").Preload("Bookmark").Preload("Note").Preload("Tags").First(&timeEntry)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"time_entry": timeEntry})
|
||||
}
|
||||
|
||||
// DeleteTimeEntry deletes a time entry
|
||||
func (h *TimeEntryHandler) DeleteTimeEntry(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid time entry ID"})
|
||||
return
|
||||
}
|
||||
|
||||
var timeEntry models.TimeEntry
|
||||
if err := h.db.Where("id = ? AND user_id = ?", id, userID).First(&timeEntry).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Time entry not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve time entry"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&timeEntry).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete time entry"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Time entry deleted successfully"})
|
||||
}
|
||||
|
||||
// GetTimeStats retrieves time tracking statistics
|
||||
func (h *TimeEntryHandler) GetTimeStats(c *gin.Context) {
|
||||
userID := c.GetUint("user_id")
|
||||
|
||||
var stats struct {
|
||||
TotalTimeSeconds int64 `json:"total_time_seconds"`
|
||||
TotalEntries int64 `json:"total_entries"`
|
||||
RunningEntries int64 `json:"running_entries"`
|
||||
BillableTime int64 `json:"billable_time_seconds"`
|
||||
TotalBillable float64 `json:"total_billable_amount"`
|
||||
}
|
||||
|
||||
// Total time and entries
|
||||
h.db.Model(&models.TimeEntry{}).
|
||||
Where("user_id = ?", userID).
|
||||
Select("COALESCE(SUM(duration), 0) as total_time_seconds, COUNT(*) as total_entries").
|
||||
Scan(&stats)
|
||||
|
||||
// Running entries
|
||||
h.db.Model(&models.TimeEntry{}).
|
||||
Where("user_id = ? AND is_running = ?", userID, true).
|
||||
Count(&stats.RunningEntries)
|
||||
|
||||
// Billable time and amount
|
||||
var billableStats struct {
|
||||
BillableTime int64 `json:"billable_time"`
|
||||
TotalBillable float64 `json:"total_billable"`
|
||||
}
|
||||
|
||||
h.db.Model(&models.TimeEntry{}).
|
||||
Where("user_id = ? AND billable = ?", userID, true).
|
||||
Select("COALESCE(SUM(duration), 0) as billable_time, COALESCE(SUM(duration * hourly_rate / 3600), 0) as total_billable").
|
||||
Scan(&billableStats)
|
||||
|
||||
stats.BillableTime = billableStats.BillableTime
|
||||
stats.TotalBillable = billableStats.TotalBillable
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"stats": stats})
|
||||
}
|
||||
Reference in New Issue
Block a user