379 lines
8.6 KiB
Go
379 lines
8.6 KiB
Go
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
|
||
}
|