Loggest thine Stuff
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.
 
 
 
 
 
 

426 lines
9.6 KiB

package items
import (
"context"
"fmt"
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/internal/genutils"
"git.aiterp.net/stufflog3/stufflog3/internal/validate"
"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 len(filter.ScopeIDs) > 0 {
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 {
scope := &sc.Scope
if scope.ID != item.ScopeID {
userScopes, err := sc.Scopes(ctx)
if err != nil {
return nil, err
}
for _, userScope := range userScopes {
if userScope.ID == item.ScopeID {
scope = &userScope
break
}
}
if scope.ID != item.ScopeID {
return nil, models.NotFoundError("Scope")
}
}
res = append(res, generateResult(item, *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)
for i, tag := range item.Tags {
if !validate.Tag(tag) {
return nil, models.BadInputError{
Object: "ItemInput",
Field: "tags",
Problem: fmt.Sprintf("Invalid tag: %s", tag),
Element: tag,
}
}
for _, prevTag := range item.Tags[:i] {
if strings.EqualFold(tag, prevTag) {
return nil, models.BadInputError{
Object: "ItemInput",
Field: "tags",
Problem: fmt.Sprintf("Duplicate tag: %s", tag),
Element: tag,
}
}
}
}
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
}
err = validate.Tags(item.Tags, update.AddTags, update.RemoveTags)
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
}