You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
378 lines
8.6 KiB
378 lines
8.6 KiB
package items
|
|
|
|
import (
|
|
"context"
|
|
"git.aiterp.net/stufflog3/stufflog3/entities"
|
|
"git.aiterp.net/stufflog3/stufflog3/internal/genutils"
|
|
"git.aiterp.net/stufflog3/stufflog3/models"
|
|
"git.aiterp.net/stufflog3/stufflog3/usecases/auth"
|
|
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
|
|
"git.aiterp.net/stufflog3/stufflog3/usecases/stats"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type Service struct {
|
|
Auth *auth.Service
|
|
Scopes *scopes.Service
|
|
Stats *stats.Service
|
|
Repository Repository
|
|
ProjectFetcher ProjectFetcher
|
|
RequirementFetcher RequirementFetcher
|
|
}
|
|
|
|
func (s *Service) Find(ctx context.Context, id int) (*Result, error) {
|
|
sc := s.Scopes.Context(ctx)
|
|
if sc == nil {
|
|
return nil, models.PermissionDeniedError{}
|
|
}
|
|
|
|
item, err := s.Repository.Find(ctx, sc.ID, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
itemStats, err := s.Repository.ListStat(ctx, *item)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var requirements []entities.Requirement
|
|
if item.RequirementID != nil {
|
|
requirements, _, err = s.RequirementFetcher.FetchRequirements(ctx, item.ScopeID, *item.RequirementID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
var projects []entities.Project
|
|
if item.ProjectID != nil {
|
|
projects, err = s.ProjectFetcher.FetchProjects(ctx, item.ScopeID, *item.ProjectID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
result := generateResult(*item, sc.Scope, itemStats, requirements, projects)
|
|
return &result, nil
|
|
}
|
|
|
|
func (s *Service) ListAllScopes(ctx context.Context, filter models.ItemFilter) ([]Result, error) {
|
|
userScopes, err := s.Scopes.Context(ctx).Scopes(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(userScopes) == 0 {
|
|
return []Result{}, nil
|
|
}
|
|
|
|
filter.ScopeIDs = make([]int, 0, len(userScopes))
|
|
for _, scope := range userScopes {
|
|
filter.ScopeIDs = append(filter.ScopeIDs, scope.ID)
|
|
}
|
|
|
|
items, err := s.Repository.Fetch(ctx, filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
progresses, err := s.Repository.ListStat(ctx, items...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
reqIds := genutils.Set[int]{}
|
|
projIds := genutils.Set[int]{}
|
|
for _, item := range items {
|
|
if item.RequirementID != nil {
|
|
reqIds.Add(*item.RequirementID)
|
|
}
|
|
if item.ProjectID != nil {
|
|
projIds.Add(*item.ProjectID)
|
|
}
|
|
}
|
|
var requirements []entities.Requirement
|
|
if reqIds.Len() > 0 {
|
|
requirements, _, err = s.RequirementFetcher.FetchRequirements(ctx, -1, reqIds.Values()...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
var projects []entities.Project
|
|
if projIds.Len() > 0 {
|
|
projects, err = s.ProjectFetcher.FetchProjects(ctx, -1, projIds.Values()...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
res := make([]Result, 0, len(items))
|
|
for _, item := range items {
|
|
for _, scope := range userScopes {
|
|
if item.ScopeID == scope.ID {
|
|
res = append(res, generateResult(item, scope, progresses, requirements, projects))
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func (s *Service) ListScoped(ctx context.Context, filter models.ItemFilter) ([]Result, error) {
|
|
sc := s.Scopes.Context(ctx)
|
|
if sc == nil {
|
|
return nil, models.PermissionDeniedError{}
|
|
}
|
|
|
|
if filter.ScopeIDs != nil {
|
|
userScopes, err := sc.Scopes(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, id := range filter.ScopeIDs {
|
|
found := false
|
|
for _, scope := range userScopes {
|
|
if scope.ID == id {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !found {
|
|
return nil, models.ForbiddenError("Scope you do not belong to found in filter scope IDs.")
|
|
}
|
|
}
|
|
} else {
|
|
filter.ScopeIDs = []int{sc.ID}
|
|
}
|
|
|
|
items, err := s.Repository.Fetch(ctx, filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
progresses, err := s.Repository.ListStat(ctx, items...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
reqIds := genutils.Set[int]{}
|
|
projIds := genutils.Set[int]{}
|
|
for _, item := range items {
|
|
if item.RequirementID != nil {
|
|
reqIds.Add(*item.RequirementID)
|
|
}
|
|
if item.ProjectID != nil {
|
|
projIds.Add(*item.ProjectID)
|
|
}
|
|
}
|
|
var requirements []entities.Requirement
|
|
if reqIds.Len() > 0 {
|
|
requirements, _, err = s.RequirementFetcher.FetchRequirements(ctx, sc.ID, reqIds.Values()...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
var projects []entities.Project
|
|
if projIds.Len() > 0 {
|
|
projects, err = s.ProjectFetcher.FetchProjects(ctx, sc.ID, projIds.Values()...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
res := make([]Result, 0, len(items))
|
|
for _, item := range items {
|
|
res = append(res, generateResult(item, sc.Scope, progresses, requirements, projects))
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func (s *Service) Create(ctx context.Context, item entities.Item, stats []entities.ItemStat) (*Result, error) {
|
|
scope := s.Scopes.Context(ctx).Scope
|
|
user := s.Auth.GetUser(ctx)
|
|
|
|
item.Name = strings.Trim(item.Name, " \t\r\n")
|
|
if item.Name == "" {
|
|
return nil, models.BadInputError{
|
|
Object: "ItemInput",
|
|
Field: "name",
|
|
Problem: "Item name cannot be empty",
|
|
}
|
|
}
|
|
var requirements []entities.Requirement
|
|
var projects []entities.Project
|
|
if item.RequirementID != nil {
|
|
reqs, _, err := s.RequirementFetcher.FetchRequirements(ctx, scope.ID, *item.RequirementID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(reqs) == 0 {
|
|
return nil, models.NotFoundError("Requirement")
|
|
}
|
|
|
|
item.ProjectID = &reqs[0].ProjectID
|
|
requirements = reqs
|
|
|
|
projects, _ = s.ProjectFetcher.FetchProjects(ctx, scope.ID, *item.ProjectID)
|
|
}
|
|
item.ScopeID = scope.ID
|
|
item.OwnerID = user.ID
|
|
|
|
if item.CreatedTime.IsZero() {
|
|
item.CreatedTime = time.Now()
|
|
}
|
|
|
|
newItem, err := s.Repository.Insert(ctx, item)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
goodStats := make([]entities.ItemStat, 0, len(stats))
|
|
for _, stat := range stats {
|
|
stat.ItemID = newItem.ID
|
|
if scope.Stat(stat.StatID) == nil {
|
|
return nil, models.NotFoundError("Stat")
|
|
}
|
|
|
|
err := s.Repository.UpdateStat(ctx, stat)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
goodStats = append(goodStats, stat)
|
|
}
|
|
|
|
result := generateResult(*newItem, scope, goodStats, requirements, projects)
|
|
return &result, nil
|
|
}
|
|
|
|
func (s *Service) Update(ctx context.Context, id int, update models.ItemUpdate, stats []entities.ItemStat) (*Result, error) {
|
|
scope := s.Scopes.Context(ctx).Scope
|
|
if update.OwnerID != nil {
|
|
if _, ok := scope.Members[*update.OwnerID]; !ok {
|
|
return nil, models.NotFoundError("Member")
|
|
}
|
|
}
|
|
|
|
if update.Name != nil && *update.Name == "" {
|
|
return nil, models.BadInputError{
|
|
Object: "ItemUpdate",
|
|
Field: "name",
|
|
Problem: "Item name cannot be empty",
|
|
}
|
|
}
|
|
|
|
item, err := s.Find(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if update.RequirementID != nil {
|
|
if *update.RequirementID > 0 {
|
|
req, _, err := s.RequirementFetcher.FetchRequirements(ctx, scope.ID, *update.RequirementID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(req) == 0 {
|
|
return nil, models.NotFoundError("Requirement")
|
|
}
|
|
|
|
item.ProjectID = genutils.Ptr(req[0].ProjectID)
|
|
item.Requirement = &ResultRequirement{
|
|
ID: req[0].ID,
|
|
Name: req[0].Name,
|
|
Status: req[0].Status,
|
|
StatusName: scope.StatusName(req[0].Status),
|
|
}
|
|
|
|
projects, err := s.ProjectFetcher.FetchProjects(ctx, scope.ID, *item.ProjectID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(projects) == 1 {
|
|
item.Project = &ResultProject{
|
|
ID: projects[0].ID,
|
|
CreatedTime: projects[0].CreatedTime,
|
|
Name: projects[0].Name,
|
|
Status: projects[0].Status,
|
|
StatusName: scope.StatusName(projects[0].Status),
|
|
}
|
|
}
|
|
} else {
|
|
item.ProjectID = nil
|
|
}
|
|
}
|
|
|
|
err = s.Repository.Update(ctx, item.Item, update)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
item.Item.ApplyUpdate(update)
|
|
|
|
for _, stat := range stats {
|
|
stat.ItemID = item.ID
|
|
|
|
err = s.Repository.UpdateStat(ctx, stat)
|
|
if err == nil {
|
|
item.AddStat(scope, stat)
|
|
}
|
|
}
|
|
|
|
return item, nil
|
|
}
|
|
|
|
func (s *Service) Delete(ctx context.Context, id int) (*Result, error) {
|
|
item, err := s.Find(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = s.Repository.Delete(ctx, item.Item)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return item, nil
|
|
}
|
|
|
|
func (s *Service) UpdateStat(ctx context.Context, itemID int, stat entities.ItemStat) (*Result, error) {
|
|
scope := s.Scopes.Context(ctx).Scope
|
|
item, err := s.Find(ctx, itemID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
stat.ItemID = itemID
|
|
|
|
err = s.Repository.UpdateStat(ctx, stat)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
item.AddStat(scope, stat)
|
|
|
|
return item, nil
|
|
}
|
|
|
|
func (s *Service) DeleteStat(ctx context.Context, itemID int, statID int) (*Result, error) {
|
|
item, err := s.Find(ctx, itemID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
err = s.Repository.UpdateStat(ctx, entities.ItemStat{
|
|
ItemID: itemID,
|
|
StatID: statID,
|
|
Acquired: 0,
|
|
Required: -1,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
item.RemoveStat(statID)
|
|
|
|
return item, nil
|
|
}
|