package projects import ( "errors" "strings" "gorm.io/gorm" "senlinai-agent/backend/internal/models" ) func (s *Service) CreateTask(ownerID uint, projectID uint, input CreateTaskInput) (*models.SenlinAgentTask, error) { if err := ensureProjectOwner(ownerID, projectID); err != nil { return nil, err } title := strings.TrimSpace(input.Title) if title == "" { return nil, errors.New("task title is required") } status := strings.TrimSpace(input.Status) if status == "" { status = "open" } tagID, err := s.findOrCreateTagID(projectID, input.Tag) if err != nil { return nil, err } task := &models.SenlinAgentTask{ ProjectID: projectID, CreatedBy: ownerID, TagID: tagID, Title: title, Description: strings.TrimSpace(input.Description), Status: status, DueAt: input.DueAt, } return task, models.DBService.Create(task).Error } func (s *Service) UpdateTask(ownerID uint, projectID uint, taskID uint, input UpdateTaskInput) (*models.SenlinAgentTask, error) { if err := ensureProjectOwner(ownerID, projectID); err != nil { return nil, err } var task models.SenlinAgentTask if err := models.DBService.Where("id = ? AND project_id = ?", taskID, projectID).First(&task).Error; err != nil { return nil, err } nextProjectID := input.NextProjectID if nextProjectID == 0 { nextProjectID = task.ProjectID } if err := ensureProjectOwner(ownerID, nextProjectID); err != nil { return nil, err } title := strings.TrimSpace(input.Title) if title == "" { return nil, errors.New("task title is required") } tagID, err := s.findOrCreateTagID(nextProjectID, input.Tag) if err != nil { return nil, err } projectIdentity, err := projectIdentity(nextProjectID) if err != nil { return nil, err } tagIdentity, err := optionalTagIdentity(tagID) if err != nil { return nil, err } status := strings.TrimSpace(input.Status) if status == "" { if input.Completed { status = "done" } else { status = "open" } } task.ProjectID = nextProjectID task.ProjectIdentity = projectIdentity task.Title = title task.Description = strings.TrimSpace(input.Description) task.Status = status task.TagID = tagID task.TagIdentity = tagIdentity if err := models.DBService.Save(&task).Error; err != nil { return nil, err } return &task, nil } func (s *Service) CreateFileSource(ownerID uint, projectID uint, input CreateFileSourceInput) (*models.SenlinAgentSource, error) { if err := ensureProjectOwner(ownerID, projectID); err != nil { return nil, err } title := strings.TrimSpace(input.Title) if title == "" { return nil, errors.New("source title is required") } filePath := strings.TrimSpace(input.FilePath) if filePath == "" { return nil, errors.New("source file path is required") } source := &models.SenlinAgentSource{ProjectID: projectID, CreatedBy: ownerID, Kind: "file", Title: title, FilePath: filePath} return source, models.DBService.Create(source).Error } func (s *Service) CreateCronPlan(ownerID uint, projectID uint, input CreateCronPlanInput) (*models.SenlinAgentCronPlan, error) { if err := ensureProjectOwner(ownerID, projectID); err != nil { return nil, err } title := strings.TrimSpace(input.Title) if title == "" { return nil, errors.New("cron plan title is required") } schedule := strings.TrimSpace(input.Schedule) if schedule == "" { return nil, errors.New("cron schedule is required") } plan := &models.SenlinAgentCronPlan{ ProjectID: projectID, CreatedBy: ownerID, Title: title, Schedule: schedule, Enabled: input.Enabled, NextRunAt: input.NextRunAt, LastResult: "Not run yet", } return plan, models.DBService.Create(plan).Error } func (s *Service) CreateProjectTag(ownerID uint, projectID uint, name string) (*models.SenlinAgentTag, error) { if err := ensureProjectOwner(ownerID, projectID); err != nil { return nil, err } return s.CreateTag(projectID, name) } func (s *Service) CreateTag(projectID uint, name string) (*models.SenlinAgentTag, error) { name = strings.TrimSpace(name) if name == "" { return nil, errors.New("tag name is required") } var existing models.SenlinAgentTag err := models.DBService.Where("project_id = ? AND name = ?", projectID, name).First(&existing).Error if err == nil { return &existing, nil } if !errors.Is(err, gorm.ErrRecordNotFound) { return nil, err } tag := &models.SenlinAgentTag{ProjectID: projectID, Name: name} return tag, models.DBService.Create(tag).Error } func (s *Service) ListProjectTags(ownerID uint, projectID uint) ([]models.SenlinAgentTag, error) { if err := ensureProjectOwner(ownerID, projectID); err != nil { return nil, err } return s.ListTags(projectID) } func (s *Service) ListTags(projectID uint) ([]models.SenlinAgentTag, error) { var tags []models.SenlinAgentTag err := models.DBService.Where("project_id = ?", projectID).Order("name asc").Find(&tags).Error return tags, err } func (s *Service) findOrCreateTagID(projectID uint, name string) (*uint, error) { name = strings.TrimSpace(name) if name == "" { return nil, nil } tag, err := s.CreateTag(projectID, name) if err != nil { return nil, err } return &tag.ID, nil } func ensureProjectOwner(ownerID uint, projectID uint) error { var count int64 if err := models.DBService.Model(&models.SenlinAgentProject{}).Where("id = ? AND owner_id = ?", projectID, ownerID).Count(&count).Error; err != nil { return err } if count == 0 { return errors.New("project not found") } return nil } func projectIdentity(projectID uint) (string, error) { var project models.SenlinAgentProject if err := models.DBService.Select("identity").First(&project, projectID).Error; err != nil { return "", err } return project.Identity, nil } func optionalTagIdentity(tagID *uint) (*string, error) { if tagID == nil { return nil, nil } var tag models.SenlinAgentTag if err := models.DBService.Select("identity").First(&tag, *tagID).Error; err != nil { return nil, err } return &tag.Identity, nil }