Browse Source

added remaining item endpoints.

master
Gisle Aune 2 years ago
parent
commit
cb22fc701f
  1. 8
      cmd/stufflog3-local/main.go
  2. 2
      entities/item.go
  3. 16
      models/date.go
  4. 90
      ports/httpapi/items.go
  5. 3
      ports/httpapi/projects.go
  6. 18
      ports/mysql/items.go
  7. 8
      usecases/items/repository.go
  8. 43
      usecases/items/result.go
  9. 139
      usecases/items/service.go
  10. 6
      usecases/projects/result.go

8
cmd/stufflog3-local/main.go

@ -57,9 +57,11 @@ func main() {
Repository: db.Stats(),
}
itemsService := &items.Service{
Scopes: scopesService,
Stats: statsService,
Repository: db.Items(),
Auth: authService,
Scopes: scopesService,
Stats: statsService,
Repository: db.Items(),
RequirementFetcher: db.Projects(),
}
projectsService := &projects.Service{
Auth: authService,

2
entities/item.go

@ -18,7 +18,7 @@ type Item struct {
ScheduledDate *models.Date `json:"scheduledDate"`
}
type ItemProgress struct {
type ItemStat struct {
ItemID int `json:"itemId"`
StatID int `json:"statId"`
Acquired int `json:"acquired"`

16
models/date.go

@ -38,6 +38,14 @@ func ParseDate(s string) (Date, error) {
return Date{y, int(m), d}, nil
}
func (d Date) String() string {
return fmt.Sprintf("%04d-%02d-%02d", d[0], d[1], d[2])
}
func (d Date) ToTime() time.Time {
return time.Date(d[0], time.Month(d[1]), d[2], 0, 0, 0, 0, time.UTC)
}
func (d *Date) UnmarshalJSON(b []byte) error {
var str string
err := json.Unmarshal(b, &str)
@ -53,14 +61,6 @@ func (d *Date) UnmarshalJSON(b []byte) error {
return nil
}
func (d Date) String() string {
return fmt.Sprintf("%04d-%02d-%02d", d[0], d[1], d[2])
}
func (d Date) MarshalJSON() ([]byte, error) {
return []byte("\"" + d.String() + "\""), nil
}
func (d Date) ToTime() time.Time {
return time.Date(d[0], time.Month(d[1]), d[2], 0, 0, 0, 0, time.UTC)
}

90
ports/httpapi/items.go

@ -2,6 +2,7 @@ package httpapi
import (
"fmt"
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/models"
"git.aiterp.net/stufflog3/stufflog3/usecases/items"
"github.com/gin-gonic/gin"
@ -11,6 +12,15 @@ import (
)
func Items(g *gin.RouterGroup, items *items.Service) {
g.GET("/:item_id", handler("project", func(c *gin.Context) (interface{}, error) {
id, err := reqInt(c, "item_id")
if err != nil {
return nil, err
}
return items.Find(c.Request.Context(), id)
}))
g.GET("", handler("items", func(c *gin.Context) (interface{}, error) {
filter := models.ItemFilter{}
@ -172,4 +182,84 @@ func Items(g *gin.RouterGroup, items *items.Service) {
return items.ListScoped(c.Request.Context(), filter)
}))
g.POST("", handler("item", func(c *gin.Context) (interface{}, error) {
input := struct {
entities.Item
Stats []entities.ItemStat `json:"stats"`
}{}
err := c.BindJSON(&input)
if err != nil {
return nil, models.BadInputError{
Object: "Project",
Problem: "Invalid JSON: " + err.Error(),
}
}
return items.Create(c.Request.Context(), input.Item, input.Stats)
}))
g.PUT("/:item_id", handler("item", func(c *gin.Context) (interface{}, error) {
input := models.ItemUpdate{}
err := c.BindJSON(&input)
if err != nil {
return nil, models.BadInputError{
Object: "Project",
Problem: "Invalid JSON: " + err.Error(),
}
}
id, err := reqInt(c, "item_id")
if err != nil {
return nil, err
}
return items.Update(c.Request.Context(), id, input)
}))
g.PUT("/:item_id/stats/:stat_id", handler("item", func(c *gin.Context) (interface{}, error) {
input := entities.ItemStat{}
err := c.BindJSON(&input)
if err != nil {
return nil, models.BadInputError{
Object: "Project",
Problem: "Invalid JSON: " + err.Error(),
}
}
itemID, err := reqInt(c, "item_id")
if err != nil {
return nil, err
}
statID, err := reqInt(c, "stat_id")
if err != nil {
return nil, err
}
input.StatID = statID
return items.UpdateStat(c.Request.Context(), itemID, input)
}))
g.DELETE("/:item_id/stats/:stat_id", handler("item", func(c *gin.Context) (interface{}, error) {
itemID, err := reqInt(c, "item_id")
if err != nil {
return nil, err
}
statID, err := reqInt(c, "stat_id")
if err != nil {
return nil, err
}
return items.DeleteStat(c.Request.Context(), itemID, statID)
}))
g.DELETE("/:item_id", handler("item", func(c *gin.Context) (interface{}, error) {
id, err := reqInt(c, "item_id")
if err != nil {
return nil, err
}
return items.Delete(c.Request.Context(), id)
}))
}

3
ports/httpapi/projects.go

@ -8,7 +8,6 @@ import (
)
func Projects(g *gin.RouterGroup, service *projects.Service) {
g.GET("", handler("projects", func(c *gin.Context) (interface{}, error) {
return service.List(c.Request.Context())
}))
@ -49,7 +48,7 @@ func Projects(g *gin.RouterGroup, service *projects.Service) {
return project.Requirements, nil
}))
g.GET("/:project_id/requirements/:requirement_id", handler("requirements", func(c *gin.Context) (interface{}, error) {
g.GET("/:project_id/requirements/:requirement_id", handler("requirement", func(c *gin.Context) (interface{}, error) {
projectID, err := reqInt(c, "project_id")
if err != nil {
return nil, err

18
ports/mysql/items.go

@ -166,7 +166,7 @@ func (r *itemRepository) Fetch(ctx context.Context, filter models.ItemFilter) ([
func (r *itemRepository) Insert(ctx context.Context, item entities.Item) (*entities.Item, error) {
res, err := r.q.InsertItem(ctx, mysqlcore.InsertItemParams{
ScopeID: item.ScopeID,
ProjectRequirementID: sql.NullInt32{},
ProjectRequirementID: sqlIntPtr(item.ProjectRequirementID),
Name: item.Name,
Description: item.Description,
CreatedTime: item.CreatedTime,
@ -204,9 +204,9 @@ func (r *itemRepository) Delete(ctx context.Context, item entities.Item) error {
return r.q.DeleteItem(ctx, item.ID)
}
func (r *itemRepository) ListProgress(ctx context.Context, items ...entities.Item) ([]entities.ItemProgress, error) {
func (r *itemRepository) ListStat(ctx context.Context, items ...entities.Item) ([]entities.ItemStat, error) {
if len(items) == 0 {
return []entities.ItemProgress{}, nil
return []entities.ItemStat{}, nil
}
ids := make([]interface{}, 0, 64)
@ -222,15 +222,15 @@ func (r *itemRepository) ListProgress(ctx context.Context, items ...entities.Ite
rows, err := r.db.QueryContext(ctx, query, ids...)
if err != nil {
if err == sql.ErrNoRows {
return []entities.ItemProgress{}, nil
return []entities.ItemStat{}, nil
}
return nil, err
}
res := make([]entities.ItemProgress, 0, 8)
res := make([]entities.ItemStat, 0, 8)
for rows.Next() {
progress := entities.ItemProgress{}
progress := entities.ItemStat{}
err = rows.Scan(&progress.ItemID, &progress.StatID, &progress.Acquired, &progress.Required)
if err != nil {
@ -243,7 +243,7 @@ func (r *itemRepository) ListProgress(ctx context.Context, items ...entities.Ite
return res, nil
}
func (r *itemRepository) UpdateProgress(ctx context.Context, item entities.ItemProgress) error {
func (r *itemRepository) UpdateStat(ctx context.Context, item entities.ItemStat) error {
if item.Required <= 0 {
return r.q.DeleteItemStatProgress(ctx, mysqlcore.DeleteItemStatProgressParams{ItemID: item.ItemID, StatID: item.StatID})
}
@ -251,7 +251,7 @@ func (r *itemRepository) UpdateProgress(ctx context.Context, item entities.ItemP
return r.q.ReplaceItemStatProgress(ctx, mysqlcore.ReplaceItemStatProgressParams{
ItemID: item.ItemID,
StatID: item.StatID,
Acquired: item.Required,
Required: item.Acquired,
Acquired: item.Acquired,
Required: item.Required,
})
}

8
usecases/items/repository.go

@ -12,6 +12,10 @@ type Repository interface {
Insert(ctx context.Context, item entities.Item) (*entities.Item, error)
Update(ctx context.Context, item entities.Item, update models.ItemUpdate) error
Delete(ctx context.Context, item entities.Item) error
ListProgress(ctx context.Context, items ...entities.Item) ([]entities.ItemProgress, error)
UpdateProgress(ctx context.Context, progress entities.ItemProgress) error
ListStat(ctx context.Context, items ...entities.Item) ([]entities.ItemStat, error)
UpdateStat(ctx context.Context, progress entities.ItemStat) error
}
type RequirementFetcher interface {
FetchRequirements(ctx context.Context, scopeID int, requirementIDs ...int) ([]entities.Requirement, []entities.RequirementStat, error)
}

43
usecases/items/result.go

@ -3,6 +3,7 @@ package items
import (
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
"sort"
)
type Result struct {
@ -21,6 +22,44 @@ func (r *Result) Stat(statID int) *ResultStat {
return nil
}
func (r *Result) AddStat(scope scopes.Result, stat entities.ItemStat) {
for i, stat2 := range r.Stats {
if stat2.ID == stat.StatID {
r.Stats[i].Acquired = stat.Acquired
r.Stats[i].Required = stat.Required
return
}
}
scopeStat := scope.Stat(stat.StatID)
if scopeStat == nil {
return
}
r.Stats = append(r.Stats, ResultStat{
ID: scopeStat.ID,
Name: scopeStat.Name,
Weight: scopeStat.Weight,
Acquired: stat.Acquired,
Required: stat.Required,
})
r.SortStats()
}
func (r *Result) SortStats() {
sort.Slice(r.Stats, func(i, j int) bool {
return r.Stats[i].Name < r.Stats[j].Name
})
}
func (r *Result) RemoveStat(id int) {
for i, stat := range r.Stats {
if stat.ID == id {
r.Stats = append(r.Stats[:i], r.Stats[i+1:]...)
}
}
}
type ResultStat struct {
ID int `json:"id"`
Name string `json:"name"`
@ -29,7 +68,7 @@ type ResultStat struct {
Required int `json:"required"`
}
func generateResult(item entities.Item, scope scopes.Result, progresses []entities.ItemProgress) Result {
func generateResult(item entities.Item, scope scopes.Result, progresses []entities.ItemStat) Result {
res := Result{
Item: item,
Stats: make([]ResultStat, 0, 8),
@ -55,5 +94,7 @@ func generateResult(item entities.Item, scope scopes.Result, progresses []entiti
}
}
res.SortStats()
return res
}

139
usecases/items/service.go

@ -2,15 +2,21 @@ package items
import (
"context"
"git.aiterp.net/stufflog3/stufflog3/entities"
"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 {
Scopes *scopes.Service
Stats *stats.Service
Repository Repository
Auth *auth.Service
Scopes *scopes.Service
Stats *stats.Service
Repository Repository
RequirementFetcher RequirementFetcher
}
func (s *Service) Find(ctx context.Context, id int) (*Result, error) {
@ -24,12 +30,12 @@ func (s *Service) Find(ctx context.Context, id int) (*Result, error) {
return nil, err
}
progresses, err := s.Repository.ListProgress(ctx, *item)
itemStats, err := s.Repository.ListStat(ctx, *item)
if err != nil {
return nil, err
}
result := generateResult(*item, sc.Scope, progresses)
result := generateResult(*item, sc.Scope, itemStats)
return &result, nil
}
@ -67,7 +73,7 @@ func (s *Service) ListScoped(ctx context.Context, filter models.ItemFilter) ([]R
return nil, err
}
progresses, err := s.Repository.ListProgress(ctx, items...)
progresses, err := s.Repository.ListStat(ctx, items...)
if err != nil {
return nil, err
}
@ -79,3 +85,124 @@ func (s *Service) ListScoped(ctx context.Context, filter models.ItemFilter) ([]R
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: "ProjectInput",
Field: "name",
Problem: "Empty name provided",
}
}
if item.ProjectRequirementID != nil {
reqs, _, err := s.RequirementFetcher.FetchRequirements(ctx, scope.ID, *item.ProjectRequirementID)
if err != nil {
return nil, err
}
if len(reqs) == 0 {
return nil, models.NotFoundError("Requirement")
}
item.ProjectID = &reqs[0].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)
}
results := generateResult(*newItem, scope, goodStats)
return &results, nil
}
func (s *Service) Update(ctx context.Context, id int, update models.ItemUpdate) (*Result, error) {
item, err := s.Find(ctx, id)
if err != nil {
return nil, err
}
err = s.Repository.Update(ctx, item.Item, update)
if err != nil {
return nil, err
}
item.Item.ApplyUpdate(update)
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
}

6
usecases/projects/result.go

@ -5,6 +5,7 @@ import (
"git.aiterp.net/stufflog3/stufflog3/models"
"git.aiterp.net/stufflog3/stufflog3/usecases/items"
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
"sort"
"time"
)
@ -157,5 +158,10 @@ func generateRequirementResult(req entities.Requirement, scope scopes.Result, re
resReq.Items = append(resReq.Items, item)
}
sort.Slice(resReq.Stats, func(i, j int) bool {
return resReq.Stats[i].Name < resReq.Stats[j].Name
})
return resReq
}
Loading…
Cancel
Save