
Features: - Complete project management system with Epic/Story/Task hierarchy - Vue.js 3 + Element Plus frontend with kanban board - Go backend with Gin framework and GORM - OAuth2 integration with Gitea - Docker containerization with MySQL - RESTful API for project, task, and user management - JWT authentication and authorization - Responsive web interface with dashboard
166 lines
4.7 KiB
Go
166 lines
4.7 KiB
Go
package models
|
|
|
|
import (
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type Task struct {
|
|
ID uint `json:"id" gorm:"primaryKey"`
|
|
StoryID *uint `json:"story_id"`
|
|
ProjectID uint `json:"project_id"`
|
|
Title string `json:"title" gorm:"not null"`
|
|
Description string `json:"description"`
|
|
Status string `json:"status" gorm:"default:'todo'"`
|
|
Priority string `json:"priority" gorm:"default:'medium'"`
|
|
TaskType string `json:"task_type" gorm:"default:'feature'"`
|
|
AssigneeID *uint `json:"assignee_id"`
|
|
ReporterID uint `json:"reporter_id"`
|
|
EstimatedHours *float64 `json:"estimated_hours"`
|
|
ActualHours float64 `json:"actual_hours" gorm:"default:0"`
|
|
DueDate *time.Time `json:"due_date"`
|
|
Story *Story `json:"story" gorm:"foreignKey:StoryID"`
|
|
Project Project `json:"project" gorm:"foreignKey:ProjectID"`
|
|
Assignee *User `json:"assignee" gorm:"foreignKey:AssigneeID"`
|
|
Reporter User `json:"reporter" gorm:"foreignKey:ReporterID"`
|
|
GiteaRelations []GiteaRelation `json:"gitea_relations" gorm:"foreignKey:TaskID"`
|
|
TimeLogs []TimeLog `json:"time_logs" gorm:"foreignKey:TaskID"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
type TaskStatus string
|
|
|
|
const (
|
|
TaskStatusTodo TaskStatus = "todo"
|
|
TaskStatusInProgress TaskStatus = "in_progress"
|
|
TaskStatusReview TaskStatus = "review"
|
|
TaskStatusTesting TaskStatus = "testing"
|
|
TaskStatusDone TaskStatus = "done"
|
|
TaskStatusBlocked TaskStatus = "blocked"
|
|
)
|
|
|
|
type TaskType string
|
|
|
|
const (
|
|
TaskTypeFeature TaskType = "feature"
|
|
TaskTypeBug TaskType = "bug"
|
|
TaskTypeImprovement TaskType = "improvement"
|
|
TaskTypeResearch TaskType = "research"
|
|
TaskTypeDocumentation TaskType = "documentation"
|
|
)
|
|
|
|
func (t *Task) BeforeCreate(tx *gorm.DB) error {
|
|
return nil
|
|
}
|
|
|
|
func (t *Task) LogTime(userID uint, hours float64, description string, logDate time.Time) error {
|
|
timeLog := TimeLog{
|
|
TaskID: t.ID,
|
|
UserID: userID,
|
|
Hours: hours,
|
|
Description: description,
|
|
LogDate: logDate,
|
|
}
|
|
|
|
if err := DB.Create(&timeLog).Error; err != nil {
|
|
return err
|
|
}
|
|
|
|
t.ActualHours += hours
|
|
return DB.Save(t).Error
|
|
}
|
|
|
|
func (t *Task) LinkGiteaObject(repoID uint, repoName, relationType, objectID string, objectNumber *uint, title, url string) error {
|
|
relation := GiteaRelation{
|
|
TaskID: t.ID,
|
|
GiteaRepoID: repoID,
|
|
GiteaRepoName: repoName,
|
|
RelationType: relationType,
|
|
GiteaObjectID: objectID,
|
|
GiteaObjectNumber: objectNumber,
|
|
GiteaObjectTitle: title,
|
|
GiteaObjectURL: url,
|
|
}
|
|
|
|
return DB.Create(&relation).Error
|
|
}
|
|
|
|
func CreateTask(task *Task) error {
|
|
return DB.Create(task).Error
|
|
}
|
|
|
|
func GetTaskByID(id uint) (*Task, error) {
|
|
var task Task
|
|
err := DB.Preload("Story").Preload("Project").Preload("Assignee").
|
|
Preload("Reporter").Preload("GiteaRelations").Preload("TimeLogs").
|
|
First(&task, id).Error
|
|
return &task, err
|
|
}
|
|
|
|
func GetTasksByProject(projectID uint) ([]Task, error) {
|
|
var tasks []Task
|
|
err := DB.Where("project_id = ?", projectID).
|
|
Preload("Story").Preload("Assignee").Preload("Reporter").
|
|
Find(&tasks).Error
|
|
return tasks, err
|
|
}
|
|
|
|
func GetTasksByStory(storyID uint) ([]Task, error) {
|
|
var tasks []Task
|
|
err := DB.Where("story_id = ?", storyID).
|
|
Preload("Assignee").Preload("Reporter").
|
|
Find(&tasks).Error
|
|
return tasks, err
|
|
}
|
|
|
|
func GetTasksByAssignee(assigneeID uint) ([]Task, error) {
|
|
var tasks []Task
|
|
err := DB.Where("assignee_id = ?", assigneeID).
|
|
Preload("Story").Preload("Project").
|
|
Find(&tasks).Error
|
|
return tasks, err
|
|
}
|
|
|
|
func UpdateTask(task *Task) error {
|
|
return DB.Save(task).Error
|
|
}
|
|
|
|
func DeleteTask(id uint) error {
|
|
return DB.Delete(&Task{}, id).Error
|
|
}
|
|
|
|
func ListTasks(offset, limit int, filters map[string]interface{}) ([]Task, int64, error) {
|
|
var tasks []Task
|
|
var total int64
|
|
|
|
query := DB.Model(&Task{})
|
|
|
|
for key, value := range filters {
|
|
switch key {
|
|
case "project_id":
|
|
query = query.Where("project_id = ?", value)
|
|
case "story_id":
|
|
query = query.Where("story_id = ?", value)
|
|
case "status":
|
|
query = query.Where("status = ?", value)
|
|
case "priority":
|
|
query = query.Where("priority = ?", value)
|
|
case "task_type":
|
|
query = query.Where("task_type = ?", value)
|
|
case "assignee_id":
|
|
query = query.Where("assignee_id = ?", value)
|
|
}
|
|
}
|
|
|
|
err := query.Count(&total).Error
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
err = query.Preload("Story").Preload("Project").Preload("Assignee").
|
|
Preload("Reporter").Preload("GiteaRelations").
|
|
Offset(offset).Limit(limit).Find(&tasks).Error
|
|
return tasks, total, err
|
|
} |