Browse Source

first commit

master
Gisle Aune 2 years ago
commit
e2d098e03f
  1. 2
      .gitignore
  2. 104
      cmd/stufflog3-local/main.go
  3. 57
      entities/item.go
  4. 58
      entities/project.go
  5. 15
      entities/scope.go
  6. 59
      entities/sprint.go
  7. 42
      entities/stat.go
  8. 8
      go.mod
  9. 51
      go.sum
  10. 46
      models/date.go
  11. 47
      models/errors.go
  12. 15
      models/item.go
  13. 14
      models/project.go
  14. 6
      models/scope.go
  15. 23
      models/sprint.go
  16. 13
      models/stat.go
  17. 29
      models/status.go
  18. 71
      ports/httpapi/auth.go
  19. 68
      ports/httpapi/common.go
  20. 21
      ports/httpapi/projects.go
  21. 104
      ports/httpapi/scopes.go
  22. 90
      ports/mysql/db.go
  23. 125
      ports/mysql/items.go
  24. 598
      ports/mysql/mysqlcore/db.go
  25. 805
      ports/mysql/mysqlcore/items.sql.go
  26. 100
      ports/mysql/mysqlcore/models.go
  27. 323
      ports/mysql/mysqlcore/projects.sql.go
  28. 278
      ports/mysql/mysqlcore/scopes.sql.go
  29. 155
      ports/mysql/mysqlcore/stats.sql.go
  30. 246
      ports/mysql/projects.go
  31. 114
      ports/mysql/queries/items.sql
  32. 60
      ports/mysql/queries/projects.sql
  33. 43
      ports/mysql/queries/scopes.sql
  34. 26
      ports/mysql/queries/stats.sql
  35. 197
      ports/mysql/scopes.go
  36. 55
      ports/mysql/sqltypes/nulldate.go
  37. 36
      ports/mysql/sqltypes/nullrawmessage.go
  38. 91
      ports/mysql/stats.go
  39. 14
      scripts/goose-mysql/20220313115117_scope.sql
  40. 17
      scripts/goose-mysql/20220313115122_scope_member.sql
  41. 18
      scripts/goose-mysql/20220326173144_stat.sql
  42. 18
      scripts/goose-mysql/20220326174046_project.sql
  43. 16
      scripts/goose-mysql/20220404144911_project_requirement.sql
  44. 19
      scripts/goose-mysql/20220404144914_item.sql
  45. 17
      scripts/goose-mysql/20220404144947_item_stat_progress.sql
  46. 15
      scripts/goose-mysql/20220404184237_project_requirement_stat.sql
  47. 9
      scripts/goose-mysql/20220411190154_project_created_time.sql
  48. 22
      scripts/goose-mysql/20220502181149_sprint.sql
  49. 16
      scripts/goose-mysql/20220502181235_sprint_part.sql
  50. 30
      sqlc.yaml
  51. 22
      usecases/auth/service.go
  52. 22
      usecases/items/repository.go
  53. 28
      usecases/items/service.go
  54. 21
      usecases/projects/repository.go
  55. 143
      usecases/projects/result.go
  56. 65
      usecases/projects/service.go
  57. 20
      usecases/scopes/repository.go
  58. 73
      usecases/scopes/service.go
  59. 15
      usecases/stats/repository.go
  60. 17
      usecases/stats/service.go

2
.gitignore

@ -0,0 +1,2 @@
/.idea
.env

104
cmd/stufflog3-local/main.go

@ -0,0 +1,104 @@
package main
import (
"git.aiterp.net/stufflog3/stufflog3/ports/httpapi"
"git.aiterp.net/stufflog3/stufflog3/ports/mysql"
"git.aiterp.net/stufflog3/stufflog3/usecases/auth"
"git.aiterp.net/stufflog3/stufflog3/usecases/items"
"git.aiterp.net/stufflog3/stufflog3/usecases/projects"
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
"git.aiterp.net/stufflog3/stufflog3/usecases/stats"
"github.com/gin-gonic/gin"
"log"
"os"
"os/signal"
"strconv"
"syscall"
)
func main() {
db, err := mysql.Connect(
os.Getenv("STUFFLOG3_MYSQL_HOST"),
envInt("STUFFLOG3_MYSQL_PORT"),
os.Getenv("STUFFLOG3_MYSQL_USERNAME"),
os.Getenv("STUFFLOG3_MYSQL_PASSWORD"),
os.Getenv("STUFFLOG3_MYSQL_SCHEMA"),
)
if err != nil {
log.Println("Failed to open database:", err)
os.Exit(1)
}
authService := &auth.Service{}
scopesService := &scopes.Service{
Auth: authService,
Repository: db.Scopes(),
}
statsSerice := &stats.Service{
Scopes: scopesService,
Repository: db.Stats(),
}
itemsService := &items.Service{
Scopes: scopesService,
Stats: statsSerice,
Repository: db.Items(),
}
projectsService := &projects.Service{
Scopes: scopesService,
Stats: statsSerice,
Items: itemsService,
Repository: db.Projects(),
}
server := gin.New()
apiV1 := server.Group("/api/v1")
if os.Getenv("STUFFLOG3_USE_DUMMY_USER") != "" {
log.Println("Using dummy UUID")
apiV1.Use(httpapi.DummyMiddleware(authService, "c11230be-4912-4313-83b0-410a248b5bd1"))
} else {
apiV1.Use(httpapi.TrustingJwtParserMiddleware(authService))
}
apiV1Scopes := apiV1.Group("/scopes")
apiV1ScopesSub := apiV1Scopes.Group("/:scope_id")
apiV1ScopesSub.Use(httpapi.ScopeMiddleware(scopesService))
httpapi.Scopes(apiV1Scopes, scopesService)
httpapi.Projects(apiV1ScopesSub.Group("/projects"), projectsService)
exitSignal := make(chan os.Signal)
signal.Notify(exitSignal, os.Interrupt, os.Kill, syscall.SIGTERM)
errCh := make(chan error)
go func() {
err := server.Run(":8239")
if err != nil {
errCh <- err
}
}()
select {
case sig := <-exitSignal:
{
log.Println("Received signal", sig)
os.Exit(0)
}
case err := <-errCh:
{
log.Println("Server run failed:", err)
os.Exit(2)
}
}
}
func envInt(key string) int {
value := os.Getenv(key)
if value == "" {
return 0
}
intValue, err := strconv.Atoi(value)
if err != nil {
return 0
}
return intValue
}

57
entities/item.go

@ -0,0 +1,57 @@
package entities
import (
"git.aiterp.net/stufflog3/stufflog3/models"
"time"
)
type Item struct {
ID int `json:"id"`
ScopeID int `json:"scopeId"`
OwnerID string `json:"ownerId"`
ProjectID *int `json:"projectId,omitempty"`
ProjectRequirementID *int `json:"projectRequirementId,omitempty"`
Name string `json:"name"`
Description string `json:"description"`
CreatedTime time.Time `json:"createdTime"`
AcquiredTime *time.Time `json:"acquiredTime"`
ScheduledDate *models.Date `json:"scheduledDate"`
}
type ItemProgress struct {
ItemID int `json:"itemId"`
StatID int `json:"statId"`
Acquired int `json:"acquired"`
Required int `json:"required"`
}
func (item *Item) ApplyUpdate(update models.ItemUpdate) {
if update.ProjectRequirementID != nil {
if *update.ProjectRequirementID <= 0 {
item.ProjectRequirementID = nil
} else {
item.ProjectRequirementID = update.ProjectRequirementID
}
}
if update.OwnerID != nil {
item.OwnerID = *update.OwnerID
}
if update.Name != nil {
item.Name = *update.Name
}
if update.Description != nil {
item.Description = *update.Description
}
if update.AcquiredTime != nil {
item.AcquiredTime = update.AcquiredTime
}
if update.ScheduledDate != nil {
item.ScheduledDate = update.ScheduledDate
}
if update.ClearScheduledDate {
item.ScheduledDate = nil
}
if update.ClearAcquiredTime {
item.AcquiredTime = nil
}
}

58
entities/project.go

@ -0,0 +1,58 @@
package entities
import (
"git.aiterp.net/stufflog3/stufflog3/models"
"time"
)
type Project struct {
ID int `json:"id"`
ScopeID int `json:"scopeId"`
OwnerID string `json:"ownerId"`
CreatedTime time.Time `json:"createdTime"`
Name string `json:"name"`
Description string `json:"description"`
Status models.Status `json:"status"`
}
func (project *Project) Update(update models.ProjectUpdate) {
if update.OwnerID != nil {
project.OwnerID = *update.OwnerID
}
if update.Name != nil {
project.Name = *update.Name
}
if update.Description != nil {
project.Description = *update.Description
}
if update.Status != nil {
project.Status = *update.Status
}
}
type Requirement struct {
ID int `json:"id"`
ScopeID int `json:"scopeId"`
ProjectID int `json:"projectId"`
Name string `json:"name"`
Description string `json:"description"`
Status models.Status `json:"status"`
}
func (requirement Requirement) Update(update models.RequirementUpdate) {
if update.Name != nil {
requirement.Name = *update.Name
}
if update.Description != nil {
requirement.Description = *update.Description
}
if update.Status != nil {
requirement.Status = *update.Status
}
}
type RequirementStat struct {
RequirementID int `json:"requirementId"`
StatID int `json:"statId"`
Required int `json:"required"`
}

15
entities/scope.go

@ -0,0 +1,15 @@
package entities
type Scope struct {
ID int `json:"id"`
Name string `json:"name"`
Abbreviation string `json:"abbreviation"`
CustomLabels map[string]string `json:"customLabels"`
}
type ScopeMember struct {
ScopeID int `json:"scopeId"`
UserID string `json:"userId"`
Name string `json:"name"`
Owner bool `json:"owner"`
}

59
entities/sprint.go

@ -0,0 +1,59 @@
package entities
import (
"git.aiterp.net/stufflog3/stufflog3/models"
"time"
)
type Sprint struct {
ID int `json:"id"`
ScopeID int `json:"scopeId"`
Name string `json:"name"`
Description string `json:"description"`
Kind models.SprintKind `json:"kind"`
FromTime time.Time `json:"fromTime"`
ToTime time.Time `json:"toTime"`
IsTimed bool `json:"isTimed"`
IsCoarse bool `json:"isCoarse"`
IsUnweighted bool `json:"isUnweighted"`
AggregateName string `json:"aggregateName"`
AggregateAcquired float64 `json:"aggregateAcquired"`
AggregateRequired int `json:"aggregateRequired"`
}
// SprintPart is not meant for the frontend, but it can be submitted.
type SprintPart struct {
SprintID int `json:"sprintId"`
PartID int `json:"partId"`
Required int `json:"required,omitempty"`
}
func (sprint *Sprint) ApplyUpdate(update models.SprintUpdate) {
if update.Name != nil {
sprint.Name = *update.Name
}
if update.Description != nil {
sprint.Description = *update.Description
}
if update.FromTime != nil {
sprint.FromTime = *update.FromTime
}
if update.ToTime != nil {
sprint.ToTime = *update.ToTime
}
if update.IsTimed != nil {
sprint.IsTimed = *update.IsTimed
}
if update.IsCoarse != nil {
sprint.IsCoarse = *update.IsCoarse
}
if update.AggregateName != nil {
sprint.AggregateName = *update.AggregateName
}
if update.AggregateRequired != nil {
sprint.AggregateRequired = *update.AggregateRequired
}
}

42
entities/stat.go

@ -0,0 +1,42 @@
package entities
import "git.aiterp.net/stufflog3/stufflog3/models"
type Stat struct {
ID int `json:"id"`
Name string `json:"name"`
Weight float64 `json:"weight"`
Description string `json:"description"`
AllowedAmounts []models.StatAllowedAmount `json:"allowedAmounts"`
}
func (stat *Stat) Update(update models.StatUpdate) {
if update.Name != nil {
stat.Name = *update.Name
}
if update.Weight != nil {
stat.Weight = *update.Weight
}
if update.Description != nil {
stat.Description = *update.Description
}
for _, amount := range update.AllowedAmounts {
found := false
for i, existing := range stat.AllowedAmounts {
if existing.Label == amount.Label {
if amount.Value <= 0 {
stat.AllowedAmounts = append(stat.AllowedAmounts[:i], stat.AllowedAmounts[i+1:]...)
} else {
stat.AllowedAmounts[i].Value = amount.Value
}
found = true
break
}
}
if !found && amount.Value > 0 {
stat.AllowedAmounts = append(stat.AllowedAmounts, amount)
}
}
}

8
go.mod

@ -0,0 +1,8 @@
module git.aiterp.net/stufflog3/stufflog3
go 1.13
require (
github.com/gin-gonic/gin v1.7.7
github.com/go-sql-driver/mysql v1.6.0
)

51
go.sum

@ -0,0 +1,51 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

46
models/date.go

@ -0,0 +1,46 @@
package models
import (
"encoding/json"
"fmt"
"time"
)
type Date [3]int
func ParseDate(s string) (Date, error) {
date, err := time.ParseInLocation("2006-01-02", s, time.UTC)
if err != nil {
return [3]int{}, err
}
y, m, d := date.Date()
return Date{y, int(m), d}, nil
}
func (d *Date) UnmarshalJSON(b []byte) error {
var str string
err := json.Unmarshal(b, &str)
if err != nil {
return err
}
*d, err = ParseDate(str)
if err != nil {
return err
}
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)
}

47
models/errors.go

@ -0,0 +1,47 @@
package models
import "fmt"
type NotFoundError string
func (e NotFoundError) Error() string {
return fmt.Sprintf("%s not found or inaccessible", string(e))
}
func (e NotFoundError) HttpStatus() (int, string, interface{}) {
return 404, e.Error(), nil
}
type PermissionDeniedError struct{}
func (e PermissionDeniedError) Error() string {
return "Permission denied"
}
func (e PermissionDeniedError) HttpStatus() (int, string, interface{}) {
return 403, "Permission denied", nil
}
type BadInputError struct {
Object string
Field string
Problem string
Min interface{}
Max interface{}
}
func (e BadInputError) Error() string {
if e.Min != nil && e.Max != nil {
return fmt.Sprintf("%s.%s: %s (min: %v, max: %v)", e.Object, e.Field, e.Problem, e.Min, e.Max)
} else if e.Min != nil {
return fmt.Sprintf("%s.%s: %s (min: %v)", e.Object, e.Field, e.Problem, e.Min)
} else if e.Max != nil {
return fmt.Sprintf("%s.%s: %s (max: %v)", e.Object, e.Field, e.Problem, e.Max)
} else {
return fmt.Sprintf("%s.%s: %s", e.Object, e.Field, e.Problem)
}
}
func (e BadInputError) HttpStatus() (int, string, interface{}) {
return 400, e.Error(), &e
}

15
models/item.go

@ -0,0 +1,15 @@
package models
import "time"
type ItemUpdate struct {
ProjectRequirementID *int `json:"projectRequirementId"`
OwnerID *string `json:"ownerId"`
Name *string `json:"name"`
Description *string `json:"description"`
AcquiredTime *time.Time `json:"acquiredTime"`
ScheduledDate *Date `json:"scheduledDate"`
ClearAcquiredTime bool `json:"clearAcquiredTime"`
ClearScheduledDate bool `json:"clearScheduledDate"`
}

14
models/project.go

@ -0,0 +1,14 @@
package models
type ProjectUpdate struct {
Name *string `json:"name,omitempty"`
OwnerID *string `json:"ownerId,omitempty"`
Status *Status `json:"status,omitempty"`
Description *string `json:"description,omitempty"`
}
type RequirementUpdate struct {
Name *string `json:"name"`
Description *string `json:"description"`
Status *Status `json:"status"`
}

6
models/scope.go

@ -0,0 +1,6 @@
package models
type ScopeUpdate struct {
Name *string
Abbreviation *string
}

23
models/sprint.go

@ -0,0 +1,23 @@
package models
import "time"
type SprintUpdate struct {
Name *string `json:"name"`
Description *string `json:"description"`
FromTime *time.Time `json:"fromTime"`
ToTime *time.Time `json:"toTime"`
IsTimed *bool `json:"isTimed"`
IsCoarse *bool `json:"isCoarse"`
AggregateName *string `json:"aggregateName"`
AggregateRequired *int `json:"aggregateRequired"`
}
type SprintKind int
const (
SprintKindItems SprintKind = iota
SprintKindRequirements
SprintKindStats
SprintKindScope
)

13
models/stat.go

@ -0,0 +1,13 @@
package models
type StatUpdate struct {
Name *string `json:"name"`
Weight *float64 `json:"weight"`
Description *string `json:"description"`
AllowedAmounts []StatAllowedAmount `json:"allowedAmounts"`
}
type StatAllowedAmount struct {
Label string `json:"label"`
Value int `json:"value"`
}

29
models/status.go

@ -0,0 +1,29 @@
package models
type Status int
func (s Status) Valid() bool {
return s >= 0 && s < maxStatus
}
const (
Blocked Status = iota
Available
Background
Active
Completed
Failed
Dropped
maxStatus
)
var StatusLabels = map[Status]string{
Blocked: "Blocked",
Available: "Available",
Background: "Background",
Active: "Active",
Completed: "Completed",
Failed: "Failed",
Dropped: "Dropped",
}

71
ports/httpapi/auth.go

@ -0,0 +1,71 @@
package httpapi
import (
"context"
"encoding/base64"
"encoding/json"
"git.aiterp.net/stufflog3/stufflog3/usecases/auth"
"github.com/gin-gonic/gin"
"net/http"
"strings"
)
var contextKey = struct{}{}
func UserID(ctx context.Context) string {
if c, ok := ctx.(*gin.Context); ok {
return UserID(c.Request.Context())
}
return ctx.Value(&contextKey).(string)
}
func DummyMiddleware(auth *auth.Service, uuid string) gin.HandlerFunc {
return func(c *gin.Context) {
c.Request = c.Request.WithContext(
auth.ContextWithUser(c.Request.Context(), uuid),
)
}
}
func abortRequest(c *gin.Context) {
c.AbortWithStatusJSON(http.StatusUnauthorized, Error{
Code: http.StatusUnauthorized,
Message: "You're not supposed to be here!",
})
}
// TrustingJwtParserMiddleware is meant to be put behind an AWS API gateway that has already
// verified this token.
func TrustingJwtParserMiddleware(auth *auth.Service) gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
split := strings.Split(authHeader, ".")
if len(split) >= 3 {
data, err := base64.RawStdEncoding.DecodeString(split[1])
if err != nil {
abortRequest(c)
return
}
fields := make(map[string]interface{})
err = json.Unmarshal(data, &fields)
if err != nil {
abortRequest(c)
return
}
if sub, ok := fields["sub"].(string); ok {
c.Request = c.Request.WithContext(
auth.ContextWithUser(c.Request.Context(), sub),
)
} else {
abortRequest(c)
return
}
} else {
abortRequest(c)
}
}
}

68
ports/httpapi/common.go

@ -0,0 +1,68 @@
package httpapi
import (
"crypto/rand"
"encoding/hex"
"git.aiterp.net/stufflog3/stufflog3/models"
"github.com/gin-gonic/gin"
"log"
"strconv"
)
type statusError interface {
HttpStatus() (int, string, interface{})
}
func handler(key string, callback func(c *gin.Context) (interface{}, error)) gin.HandlerFunc {
return func(c *gin.Context) {
res, err := callback(c)
if err != nil {
if statusError, ok := err.(statusError); ok {
code, msg, data := statusError.HttpStatus()
c.JSON(code, Error{
Code: code,
Message: msg,
Data: data,
})
} else {
id := make([]byte, 16)
_, _ = rand.Read(id)
idHex := hex.EncodeToString(id)
log.Println("Internal server error", idHex, "triggered by:", err)
c.JSON(500, Error{
Code: 500,
Message: "Internal server error (" + idHex + ")",
Data: nil,
})
}
return
}
resJson := make(map[string]interface{}, 1)
resJson[key] = res
c.JSON(200, resJson)
}
}
type Error struct {
Code int `json:"statusCode"`
Message string `json:"statusMessage"`
Data interface{} `json:"errorData,omitempty"`
}
func reqInt(c *gin.Context, key string) (int, error) {
v, err := strconv.Atoi(c.Param(key))
if err != nil {
return 0, models.BadInputError{
Object: "query",
Field: key,
Problem: "Value for " + key + " must be numeric",
}
}
return v, nil
}

21
ports/httpapi/projects.go

@ -0,0 +1,21 @@
package httpapi
import (
"git.aiterp.net/stufflog3/stufflog3/usecases/projects"
"github.com/gin-gonic/gin"
)
func Projects(g *gin.RouterGroup, projects *projects.Service) {
g.GET("", handler("projects", func(c *gin.Context) (interface{}, error) {
return projects.List(c.Request.Context())
}))
g.GET("/:project_id", handler("project", func(c *gin.Context) (interface{}, error) {
id, err := reqInt(c, "project_id")
if err != nil {
return nil, err
}
return projects.Find(c.Request.Context(), id)
}))
}

104
ports/httpapi/scopes.go

@ -0,0 +1,104 @@
package httpapi
import (
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/models"
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
"github.com/gin-gonic/gin"
"net/http"
)
type scopeResult struct {
entities.Scope
Members []scopeResultMember `json:"members"`
}
type scopeResultMember struct {
ID string `json:"id"`
Name string `json:"name"`
Owner bool `json:"owner"`
}
func ScopeMiddleware(scopes *scopes.Service) gin.HandlerFunc {
return func(c *gin.Context) {
id, err := reqInt(c, "scope_id")
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, Error{
Code: http.StatusBadRequest,
Message: "Invalid scope ID in path!",
Data: models.BadInputError{
Object: "params",
Field: "scopeid",
Problem: "Not a number",
},
})
}
scope, _, err := scopes.Find(c.Request.Context(), id)
if err != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, Error{
Code: http.StatusNotFound,
Message: "Scope not found or inaccessible",
})
}
c.Request = c.Request.WithContext(scopes.ContextWithScope(c.Request.Context(), *scope))
}
}
func Scopes(g *gin.RouterGroup, scopes *scopes.Service) {
g.GET("", handler("scopes", func(c *gin.Context) (interface{}, error) {
scopes, members, err := scopes.List(c.Request.Context())
if err != nil {
return nil, err
}
results := make([]scopeResult, 0, len(scopes))
for _, scope := range scopes {
resMembers := make([]scopeResultMember, 0, len(members)/2)
for _, member := range members {
if member.ScopeID == scope.ID {
resMembers = append(resMembers, scopeResultMember{
ID: member.UserID,
Name: member.Name,
Owner: member.Owner,
})
}
}
results = append(results, scopeResult{
Scope: scope,
Members: resMembers,
})
}
return results, nil
}))
g.GET("/:scope_id", handler("scope", func(c *gin.Context) (interface{}, error) {
id, err := reqInt(c, "scope_id")
if err != nil {
return nil, err
}
scope, members, err := scopes.Find(c.Request.Context(), id)
if err != nil {
return nil, err
}
resMembers := make([]scopeResultMember, 0, len(members)/2)
for _, member := range members {
resMembers = append(resMembers, scopeResultMember{
ID: member.UserID,
Name: member.Name,
Owner: member.Owner,
})
}
return scopeResult{
Scope: *scope,
Members: resMembers,
}, nil
}))
}

90
ports/mysql/db.go

@ -0,0 +1,90 @@
package mysql
import (
"context"
"database/sql"
"fmt"
"git.aiterp.net/stufflog3/stufflog3/ports/mysql/mysqlcore"
"git.aiterp.net/stufflog3/stufflog3/usecases/items"
"git.aiterp.net/stufflog3/stufflog3/usecases/projects"
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
"git.aiterp.net/stufflog3/stufflog3/usecases/stats"
"time"
_ "github.com/go-sql-driver/mysql"
)
type Database struct {
db *sql.DB
q *mysqlcore.Queries
}
func (db *Database) Scopes() scopes.Repository {
return &scopeRepository{
db: db.db,
q: db.q,
}
}
func (db *Database) Projects() projects.Repository {
return &projectRepository{
db: db.db,
q: db.q,
}
}
func (db *Database) Stats() stats.Repository {
return &statsRepository{
db: db.db,
q: db.q,
}
}
func (db *Database) Items() items.Repository {
return &itemRepository{
db: db.db,
q: db.q,
}
}
func Connect(host string, port int, username, password, database string) (*Database, error) {
db, err := sql.Open("mysql", fmt.Sprintf(
"%s:%s@(%s:%d)/%s?parseTime=true", username, password, host, port, database,
))
if err != nil {
return nil, err
}
db.SetMaxOpenConns(10)
db.SetMaxIdleConns(10)
db.SetConnMaxIdleTime(time.Minute)
err = db.Ping()
if err != nil {
return nil, err
}
q, err := mysqlcore.Prepare(context.Background(), db)
if err != nil {
return nil, err
}
return &Database{db: db, q: q}, nil
}
func timePtr(nullTime sql.NullTime) *time.Time {
if nullTime.Valid {
return &nullTime.Time
} else {
return nil
}
}
func intPtr(nullInt32 sql.NullInt32) *int {
if nullInt32.Valid {
v := int(nullInt32.Int32)
return &v
} else {
return nil
}
}

125
ports/mysql/items.go

@ -0,0 +1,125 @@
package mysql
import (
"context"
"database/sql"
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/models"
"git.aiterp.net/stufflog3/stufflog3/ports/mysql/mysqlcore"
"strings"
"time"
)
type itemRepository struct {
db *sql.DB
q *mysqlcore.Queries
}
func (r *itemRepository) Find(ctx context.Context, scopeID, itemID int) (*entities.Item, error) {
//TODO implement me
panic("implement me")
}
func (r *itemRepository) ListCreated(ctx context.Context, scopeID int, from, to time.Time) ([]entities.Item, error) {
//TODO implement me
panic("implement me")
}
func (r *itemRepository) ListAcquired(ctx context.Context, scopeID int, from, to time.Time) ([]entities.Item, error) {
//TODO implement me
panic("implement me")
}
func (r *itemRepository) ListScheduled(ctx context.Context, scopeID int, from, to models.Date) ([]entities.Item, error) {
//TODO implement me
panic("implement me")
}
func (r *itemRepository) ListRequirement(ctx context.Context, requirementID int) ([]entities.Item, error) {
//TODO implement me
panic("implement me")
}
func (r *itemRepository) ListProject(ctx context.Context, projectID int) ([]entities.Item, error) {
rows, err := r.q.ListItemsByProject(ctx, projectID)
if err != nil {
return nil, err
}
res := make([]entities.Item, 0, len(rows))
for _, row := range rows {
res = append(res, entities.Item{
ID: row.ID,
ScopeID: row.ScopeID,
OwnerID: row.OwnerID,
ProjectID: intPtr(row.ProjectID),
ProjectRequirementID: intPtr(row.ProjectRequirementID),
Name: row.Name,
Description: row.Description,
CreatedTime: row.CreatedTime,
AcquiredTime: timePtr(row.AcquiredTime),
ScheduledDate: row.ScheduledDate.AsPtr(),
})
}
return res, nil
}
func (r *itemRepository) Insert(ctx context.Context, item entities.Item) (*entities.Item, error) {
//TODO implement me
panic("implement me")
}
func (r *itemRepository) Update(ctx context.Context, item entities.Item, update models.ItemUpdate) error {
//TODO implement me
panic("implement me")
}
func (r *itemRepository) Delete(ctx context.Context, item entities.Item) error {
//TODO implement me
panic("implement me")
}
func (r *itemRepository) ListProgress(ctx context.Context, items ...entities.Item) ([]entities.ItemProgress, error) {
if len(items) == 0 {
return []entities.ItemProgress{}, nil
}
ids := make([]interface{}, 0, 64)
for _, item := range items {
ids = append(ids, item.ID)
}
query := `
SELECT item_id, stat_id, acquired, required FROM item_stat_progress
WHERE item_id IN (?` + strings.Repeat(",?", len(ids)-1) + `);
`
rows, err := r.db.QueryContext(ctx, query, ids...)
if err != nil {
if err == sql.ErrNoRows {
return []entities.ItemProgress{}, nil
}
return nil, err
}
res := make([]entities.ItemProgress, 0, 8)
for rows.Next() {
progress := entities.ItemProgress{}
err = rows.Scan(&progress.ItemID, &progress.StatID, &progress.Acquired, &progress.Required)
if err != nil {
return nil, err
}
res = append(res, progress)
}
return res, nil
}
func (r *itemRepository) UpdateProgress(ctx context.Context, item entities.ItemProgress) error {
//TODO implement me
panic("implement me")
}

598
ports/mysql/mysqlcore/db.go

@ -0,0 +1,598 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
package mysqlcore
import (
"context"
"database/sql"
"fmt"
)
type DBTX interface {
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
func Prepare(ctx context.Context, db DBTX) (*Queries, error) {
q := Queries{db: db}
var err error
if q.clearItemProjectRequirementStmt, err = db.PrepareContext(ctx, clearItemProjectRequirement); err != nil {
return nil, fmt.Errorf("error preparing query ClearItemProjectRequirement: %w", err)
}
if q.clearItemStatProgressStmt, err = db.PrepareContext(ctx, clearItemStatProgress); err != nil {
return nil, fmt.Errorf("error preparing query ClearItemStatProgress: %w", err)
}
if q.deleteAllItemStatProgressByStatIdStmt, err = db.PrepareContext(ctx, deleteAllItemStatProgressByStatId); err != nil {
return nil, fmt.Errorf("error preparing query DeleteAllItemStatProgressByStatId: %w", err)
}
if q.deleteAllProjectRequirementStatByStatIdStmt, err = db.PrepareContext(ctx, deleteAllProjectRequirementStatByStatId); err != nil {
return nil, fmt.Errorf("error preparing query DeleteAllProjectRequirementStatByStatId: %w", err)
}
if q.deleteAllProjectRequirementStatsStmt, err = db.PrepareContext(ctx, deleteAllProjectRequirementStats); err != nil {
return nil, fmt.Errorf("error preparing query DeleteAllProjectRequirementStats: %w", err)
}
if q.deleteAllProjectRequirementsStmt, err = db.PrepareContext(ctx, deleteAllProjectRequirements); err != nil {
return nil, fmt.Errorf("error preparing query DeleteAllProjectRequirements: %w", err)
}
if q.deleteAllScopeMembersStmt, err = db.PrepareContext(ctx, deleteAllScopeMembers); err != nil {
return nil, fmt.Errorf("error preparing query DeleteAllScopeMembers: %w", err)
}
if q.deleteItemStmt, err = db.PrepareContext(ctx, deleteItem); err != nil {
return nil, fmt.Errorf("error preparing query DeleteItem: %w", err)
}
if q.deleteItemForRequirementStmt, err = db.PrepareContext(ctx, deleteItemForRequirement); err != nil {
return nil, fmt.Errorf("error preparing query DeleteItemForRequirement: %w", err)
}
if q.deleteItemStatProgressStmt, err = db.PrepareContext(ctx, deleteItemStatProgress); err != nil {
return nil, fmt.Errorf("error preparing query DeleteItemStatProgress: %w", err)
}
if q.deleteProjectStmt, err = db.PrepareContext(ctx, deleteProject); err != nil {
return nil, fmt.Errorf("error preparing query DeleteProject: %w", err)
}
if q.deleteProjectRequirementStmt, err = db.PrepareContext(ctx, deleteProjectRequirement); err != nil {
return nil, fmt.Errorf("error preparing query DeleteProjectRequirement: %w", err)
}
if q.deleteProjectRequirementStatStmt, err = db.PrepareContext(ctx, deleteProjectRequirementStat); err != nil {
return nil, fmt.Errorf("error preparing query DeleteProjectRequirementStat: %w", err)
}
if q.deleteScopeStmt, err = db.PrepareContext(ctx, deleteScope); err != nil {
return nil, fmt.Errorf("error preparing query DeleteScope: %w", err)
}
if q.deleteScopeMemberStmt, err = db.PrepareContext(ctx, deleteScopeMember); err != nil {
return nil, fmt.Errorf("error preparing query DeleteScopeMember: %w", err)
}
if q.deleteStatStmt, err = db.PrepareContext(ctx, deleteStat); err != nil {
return nil, fmt.Errorf("error preparing query DeleteStat: %w", err)
}
if q.getItemStmt, err = db.PrepareContext(ctx, getItem); err != nil {
return nil, fmt.Errorf("error preparing query GetItem: %w", err)
}
if q.getItemStatProgressBetweenStmt, err = db.PrepareContext(ctx, getItemStatProgressBetween); err != nil {
return nil, fmt.Errorf("error preparing query GetItemStatProgressBetween: %w", err)
}
if q.getProjectStmt, err = db.PrepareContext(ctx, getProject); err != nil {
return nil, fmt.Errorf("error preparing query GetProject: %w", err)
}
if q.getScopeStmt, err = db.PrepareContext(ctx, getScope); err != nil {
return nil, fmt.Errorf("error preparing query GetScope: %w", err)
}
if q.getStatStmt, err = db.PrepareContext(ctx, getStat); err != nil {
return nil, fmt.Errorf("error preparing query GetStat: %w", err)
}
if q.insertItemStmt, err = db.PrepareContext(ctx, insertItem); err != nil {
return nil, fmt.Errorf("error preparing query InsertItem: %w", err)
}
if q.insertProjectStmt, err = db.PrepareContext(ctx, insertProject); err != nil {
return nil, fmt.Errorf("error preparing query InsertProject: %w", err)
}
if q.insertProjectRequirementStmt, err = db.PrepareContext(ctx, insertProjectRequirement); err != nil {
return nil, fmt.Errorf("error preparing query InsertProjectRequirement: %w", err)
}
if q.insertScopeStmt, err = db.PrepareContext(ctx, insertScope); err != nil {
return nil, fmt.Errorf("error preparing query InsertScope: %w", err)
}
if q.insertStatStmt, err = db.PrepareContext(ctx, insertStat); err != nil {
return nil, fmt.Errorf("error preparing query InsertStat: %w", err)
}
if q.listItemStatProgressStmt, err = db.PrepareContext(ctx, listItemStatProgress); err != nil {
return nil, fmt.Errorf("error preparing query ListItemStatProgress: %w", err)
}
if q.listItemStatProgressMultiStmt, err = db.PrepareContext(ctx, listItemStatProgressMulti); err != nil {
return nil, fmt.Errorf("error preparing query ListItemStatProgressMulti: %w", err)
}
if q.listItemsAcquiredBetweenStmt, err = db.PrepareContext(ctx, listItemsAcquiredBetween); err != nil {
return nil, fmt.Errorf("error preparing query ListItemsAcquiredBetween: %w", err)
}
if q.listItemsByProjectStmt, err = db.PrepareContext(ctx, listItemsByProject); err != nil {
return nil, fmt.Errorf("error preparing query ListItemsByProject: %w", err)
}
if q.listItemsCreatedBetweenStmt, err = db.PrepareContext(ctx, listItemsCreatedBetween); err != nil {
return nil, fmt.Errorf("error preparing query ListItemsCreatedBetween: %w", err)
}
if q.listItemsCreatedBetweenNoScopeStmt, err = db.PrepareContext(ctx, listItemsCreatedBetweenNoScope); err != nil {
return nil, fmt.Errorf("error preparing query ListItemsCreatedBetweenNoScope: %w", err)
}
if q.listItemsLooseBetweenStmt, err = db.PrepareContext(ctx, listItemsLooseBetween); err != nil {
return nil, fmt.Errorf("error preparing query ListItemsLooseBetween: %w", err)
}
if q.listItemsLooseBetweenNoScopeStmt, err = db.PrepareContext(ctx, listItemsLooseBetweenNoScope); err != nil {
return nil, fmt.Errorf("error preparing query ListItemsLooseBetweenNoScope: %w", err)
}
if q.listItemsScheduledBetweenStmt, err = db.PrepareContext(ctx, listItemsScheduledBetween); err != nil {
return nil, fmt.Errorf("error preparing query ListItemsScheduledBetween: %w", err)
}
if q.listItemsScheduledBetweenNoScopeStmt, err = db.PrepareContext(ctx, listItemsScheduledBetweenNoScope); err != nil {
return nil, fmt.Errorf("error preparing query ListItemsScheduledBetweenNoScope: %w", err)
}
if q.listProjectRequirementsStmt, err = db.PrepareContext(ctx, listProjectRequirements); err != nil {
return nil, fmt.Errorf("error preparing query ListProjectRequirements: %w", err)
}
if q.listProjectRequirementsStatsStmt, err = db.PrepareContext(ctx, listProjectRequirementsStats); err != nil {
return nil, fmt.Errorf("error preparing query ListProjectRequirementsStats: %w", err)
}
if q.listProjectsStmt, err = db.PrepareContext(ctx, listProjects); err != nil {
return nil, fmt.Errorf("error preparing query ListProjects: %w", err)
}
if q.listScopeMembersStmt, err = db.PrepareContext(ctx, listScopeMembers); err != nil {
return nil, fmt.Errorf("error preparing query ListScopeMembers: %w", err)
}
if q.listScopeMembersMultiStmt, err = db.PrepareContext(ctx, listScopeMembersMulti); err != nil {
return nil, fmt.Errorf("error preparing query ListScopeMembersMulti: %w", err)
}
if q.listScopesStmt, err = db.PrepareContext(ctx, listScopes); err != nil {
return nil, fmt.Errorf("error preparing query ListScopes: %w", err)
}
if q.listScopesByUserStmt, err = db.PrepareContext(ctx, listScopesByUser); err != nil {
return nil, fmt.Errorf("error preparing query ListScopesByUser: %w", err)
}
if q.listStatsStmt, err = db.PrepareContext(ctx, listStats); err != nil {
return nil, fmt.Errorf("error preparing query ListStats: %w", err)
}
if q.replaceItemStatProgressStmt, err = db.PrepareContext(ctx, replaceItemStatProgress); err != nil {
return nil, fmt.Errorf("error preparing query ReplaceItemStatProgress: %w", err)
}
if q.replaceProjectRequirementStatStmt, err = db.PrepareContext(ctx, replaceProjectRequirementStat); err != nil {
return nil, fmt.Errorf("error preparing query ReplaceProjectRequirementStat: %w", err)
}
if q.replaceScopeMemberStmt, err = db.PrepareContext(ctx, replaceScopeMember); err != nil {
return nil, fmt.Errorf("error preparing query ReplaceScopeMember: %w", err)
}
if q.updateItemStmt, err = db.PrepareContext(ctx, updateItem); err != nil {
return nil, fmt.Errorf("error preparing query UpdateItem: %w", err)
}
if q.updateProjectStmt, err = db.PrepareContext(ctx, updateProject); err != nil {
return nil, fmt.Errorf("error preparing query UpdateProject: %w", err)
}
if q.updateProjectRequirementStmt, err = db.PrepareContext(ctx, updateProjectRequirement); err != nil {
return nil, fmt.Errorf("error preparing query UpdateProjectRequirement: %w", err)
}
if q.updateScopeStmt, err = db.PrepareContext(ctx, updateScope); err != nil {
return nil, fmt.Errorf("error preparing query UpdateScope: %w", err)
}
if q.updateStatStmt, err = db.PrepareContext(ctx, updateStat); err != nil {
return nil, fmt.Errorf("error preparing query UpdateStat: %w", err)
}
return &q, nil
}
func (q *Queries) Close() error {
var err error
if q.clearItemProjectRequirementStmt != nil {
if cerr := q.clearItemProjectRequirementStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing clearItemProjectRequirementStmt: %w", cerr)
}
}
if q.clearItemStatProgressStmt != nil {
if cerr := q.clearItemStatProgressStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing clearItemStatProgressStmt: %w", cerr)
}
}
if q.deleteAllItemStatProgressByStatIdStmt != nil {
if cerr := q.deleteAllItemStatProgressByStatIdStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteAllItemStatProgressByStatIdStmt: %w", cerr)
}
}
if q.deleteAllProjectRequirementStatByStatIdStmt != nil {
if cerr := q.deleteAllProjectRequirementStatByStatIdStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteAllProjectRequirementStatByStatIdStmt: %w", cerr)
}
}
if q.deleteAllProjectRequirementStatsStmt != nil {
if cerr := q.deleteAllProjectRequirementStatsStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteAllProjectRequirementStatsStmt: %w", cerr)
}
}
if q.deleteAllProjectRequirementsStmt != nil {
if cerr := q.deleteAllProjectRequirementsStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteAllProjectRequirementsStmt: %w", cerr)
}
}
if q.deleteAllScopeMembersStmt != nil {
if cerr := q.deleteAllScopeMembersStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteAllScopeMembersStmt: %w", cerr)
}
}
if q.deleteItemStmt != nil {
if cerr := q.deleteItemStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteItemStmt: %w", cerr)
}
}
if q.deleteItemForRequirementStmt != nil {
if cerr := q.deleteItemForRequirementStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteItemForRequirementStmt: %w", cerr)
}
}
if q.deleteItemStatProgressStmt != nil {
if cerr := q.deleteItemStatProgressStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteItemStatProgressStmt: %w", cerr)
}
}
if q.deleteProjectStmt != nil {
if cerr := q.deleteProjectStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteProjectStmt: %w", cerr)
}
}
if q.deleteProjectRequirementStmt != nil {
if cerr := q.deleteProjectRequirementStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteProjectRequirementStmt: %w", cerr)
}
}
if q.deleteProjectRequirementStatStmt != nil {
if cerr := q.deleteProjectRequirementStatStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteProjectRequirementStatStmt: %w", cerr)
}
}
if q.deleteScopeStmt != nil {
if cerr := q.deleteScopeStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteScopeStmt: %w", cerr)
}
}
if q.deleteScopeMemberStmt != nil {
if cerr := q.deleteScopeMemberStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteScopeMemberStmt: %w", cerr)
}
}
if q.deleteStatStmt != nil {
if cerr := q.deleteStatStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing deleteStatStmt: %w", cerr)
}
}
if q.getItemStmt != nil {
if cerr := q.getItemStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing getItemStmt: %w", cerr)
}
}
if q.getItemStatProgressBetweenStmt != nil {
if cerr := q.getItemStatProgressBetweenStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing getItemStatProgressBetweenStmt: %w", cerr)
}
}
if q.getProjectStmt != nil {
if cerr := q.getProjectStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing getProjectStmt: %w", cerr)
}
}
if q.getScopeStmt != nil {
if cerr := q.getScopeStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing getScopeStmt: %w", cerr)
}
}
if q.getStatStmt != nil {
if cerr := q.getStatStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing getStatStmt: %w", cerr)
}
}
if q.insertItemStmt != nil {
if cerr := q.insertItemStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing insertItemStmt: %w", cerr)
}
}
if q.insertProjectStmt != nil {
if cerr := q.insertProjectStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing insertProjectStmt: %w", cerr)
}
}
if q.insertProjectRequirementStmt != nil {
if cerr := q.insertProjectRequirementStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing insertProjectRequirementStmt: %w", cerr)
}
}
if q.insertScopeStmt != nil {
if cerr := q.insertScopeStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing insertScopeStmt: %w", cerr)
}
}
if q.insertStatStmt != nil {
if cerr := q.insertStatStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing insertStatStmt: %w", cerr)
}
}
if q.listItemStatProgressStmt != nil {
if cerr := q.listItemStatProgressStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listItemStatProgressStmt: %w", cerr)
}
}
if q.listItemStatProgressMultiStmt != nil {
if cerr := q.listItemStatProgressMultiStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listItemStatProgressMultiStmt: %w", cerr)
}
}
if q.listItemsAcquiredBetweenStmt != nil {
if cerr := q.listItemsAcquiredBetweenStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listItemsAcquiredBetweenStmt: %w", cerr)
}
}
if q.listItemsByProjectStmt != nil {
if cerr := q.listItemsByProjectStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listItemsByProjectStmt: %w", cerr)
}
}
if q.listItemsCreatedBetweenStmt != nil {
if cerr := q.listItemsCreatedBetweenStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listItemsCreatedBetweenStmt: %w", cerr)
}
}
if q.listItemsCreatedBetweenNoScopeStmt != nil {
if cerr := q.listItemsCreatedBetweenNoScopeStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listItemsCreatedBetweenNoScopeStmt: %w", cerr)
}
}
if q.listItemsLooseBetweenStmt != nil {
if cerr := q.listItemsLooseBetweenStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listItemsLooseBetweenStmt: %w", cerr)
}
}
if q.listItemsLooseBetweenNoScopeStmt != nil {
if cerr := q.listItemsLooseBetweenNoScopeStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listItemsLooseBetweenNoScopeStmt: %w", cerr)
}
}
if q.listItemsScheduledBetweenStmt != nil {
if cerr := q.listItemsScheduledBetweenStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listItemsScheduledBetweenStmt: %w", cerr)
}
}
if q.listItemsScheduledBetweenNoScopeStmt != nil {
if cerr := q.listItemsScheduledBetweenNoScopeStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listItemsScheduledBetweenNoScopeStmt: %w", cerr)
}
}
if q.listProjectRequirementsStmt != nil {
if cerr := q.listProjectRequirementsStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listProjectRequirementsStmt: %w", cerr)
}
}
if q.listProjectRequirementsStatsStmt != nil {
if cerr := q.listProjectRequirementsStatsStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listProjectRequirementsStatsStmt: %w", cerr)
}
}
if q.listProjectsStmt != nil {
if cerr := q.listProjectsStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listProjectsStmt: %w", cerr)
}
}
if q.listScopeMembersStmt != nil {
if cerr := q.listScopeMembersStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listScopeMembersStmt: %w", cerr)
}
}
if q.listScopeMembersMultiStmt != nil {
if cerr := q.listScopeMembersMultiStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listScopeMembersMultiStmt: %w", cerr)
}
}
if q.listScopesStmt != nil {
if cerr := q.listScopesStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listScopesStmt: %w", cerr)
}
}
if q.listScopesByUserStmt != nil {
if cerr := q.listScopesByUserStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listScopesByUserStmt: %w", cerr)
}
}
if q.listStatsStmt != nil {
if cerr := q.listStatsStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listStatsStmt: %w", cerr)
}
}
if q.replaceItemStatProgressStmt != nil {
if cerr := q.replaceItemStatProgressStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing replaceItemStatProgressStmt: %w", cerr)
}
}
if q.replaceProjectRequirementStatStmt != nil {
if cerr := q.replaceProjectRequirementStatStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing replaceProjectRequirementStatStmt: %w", cerr)
}
}
if q.replaceScopeMemberStmt != nil {
if cerr := q.replaceScopeMemberStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing replaceScopeMemberStmt: %w", cerr)
}
}
if q.updateItemStmt != nil {
if cerr := q.updateItemStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing updateItemStmt: %w", cerr)
}
}
if q.updateProjectStmt != nil {
if cerr := q.updateProjectStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing updateProjectStmt: %w", cerr)
}
}
if q.updateProjectRequirementStmt != nil {
if cerr := q.updateProjectRequirementStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing updateProjectRequirementStmt: %w", cerr)
}
}
if q.updateScopeStmt != nil {
if cerr := q.updateScopeStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing updateScopeStmt: %w", cerr)
}
}
if q.updateStatStmt != nil {
if cerr := q.updateStatStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing updateStatStmt: %w", cerr)
}
}
return err
}
func (q *Queries) exec(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) (sql.Result, error) {
switch {
case stmt != nil && q.tx != nil:
return q.tx.StmtContext(ctx, stmt).ExecContext(ctx, args...)
case stmt != nil:
return stmt.ExecContext(ctx, args...)
default:
return q.db.ExecContext(ctx, query, args...)
}
}
func (q *Queries) query(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) (*sql.Rows, error) {
switch {
case stmt != nil && q.tx != nil:
return q.tx.StmtContext(ctx, stmt).QueryContext(ctx, args...)
case stmt != nil:
return stmt.QueryContext(ctx, args...)
default:
return q.db.QueryContext(ctx, query, args...)
}
}
func (q *Queries) queryRow(ctx context.Context, stmt *sql.Stmt, query string, args ...interface{}) *sql.Row {
switch {
case stmt != nil && q.tx != nil:
return q.tx.StmtContext(ctx, stmt).QueryRowContext(ctx, args...)
case stmt != nil:
return stmt.QueryRowContext(ctx, args...)
default:
return q.db.QueryRowContext(ctx, query, args...)
}
}
type Queries struct {
db DBTX
tx *sql.Tx
clearItemProjectRequirementStmt *sql.Stmt
clearItemStatProgressStmt *sql.Stmt
deleteAllItemStatProgressByStatIdStmt *sql.Stmt
deleteAllProjectRequirementStatByStatIdStmt *sql.Stmt
deleteAllProjectRequirementStatsStmt *sql.Stmt
deleteAllProjectRequirementsStmt *sql.Stmt
deleteAllScopeMembersStmt *sql.Stmt
deleteItemStmt *sql.Stmt
deleteItemForRequirementStmt *sql.Stmt
deleteItemStatProgressStmt *sql.Stmt
deleteProjectStmt *sql.Stmt
deleteProjectRequirementStmt *sql.Stmt
deleteProjectRequirementStatStmt *sql.Stmt
deleteScopeStmt *sql.Stmt
deleteScopeMemberStmt *sql.Stmt
deleteStatStmt *sql.Stmt
getItemStmt *sql.Stmt
getItemStatProgressBetweenStmt *sql.Stmt
getProjectStmt *sql.Stmt
getScopeStmt *sql.Stmt
getStatStmt *sql.Stmt
insertItemStmt *sql.Stmt
insertProjectStmt *sql.Stmt
insertProjectRequirementStmt *sql.Stmt
insertScopeStmt *sql.Stmt
insertStatStmt *sql.Stmt
listItemStatProgressStmt *sql.Stmt
listItemStatProgressMultiStmt *sql.Stmt
listItemsAcquiredBetweenStmt *sql.Stmt
listItemsByProjectStmt *sql.Stmt
listItemsCreatedBetweenStmt *sql.Stmt
listItemsCreatedBetweenNoScopeStmt *sql.Stmt
listItemsLooseBetweenStmt *sql.Stmt
listItemsLooseBetweenNoScopeStmt *sql.Stmt
listItemsScheduledBetweenStmt *sql.Stmt
listItemsScheduledBetweenNoScopeStmt *sql.Stmt
listProjectRequirementsStmt *sql.Stmt
listProjectRequirementsStatsStmt *sql.Stmt
listProjectsStmt *sql.Stmt
listScopeMembersStmt *sql.Stmt
listScopeMembersMultiStmt *sql.Stmt
listScopesStmt *sql.Stmt
listScopesByUserStmt *sql.Stmt
listStatsStmt *sql.Stmt
replaceItemStatProgressStmt *sql.Stmt
replaceProjectRequirementStatStmt *sql.Stmt
replaceScopeMemberStmt *sql.Stmt
updateItemStmt *sql.Stmt
updateProjectStmt *sql.Stmt
updateProjectRequirementStmt *sql.Stmt
updateScopeStmt *sql.Stmt
updateStatStmt *sql.Stmt
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
tx: tx,
clearItemProjectRequirementStmt: q.clearItemProjectRequirementStmt,
clearItemStatProgressStmt: q.clearItemStatProgressStmt,
deleteAllItemStatProgressByStatIdStmt: q.deleteAllItemStatProgressByStatIdStmt,
deleteAllProjectRequirementStatByStatIdStmt: q.deleteAllProjectRequirementStatByStatIdStmt,
deleteAllProjectRequirementStatsStmt: q.deleteAllProjectRequirementStatsStmt,
deleteAllProjectRequirementsStmt: q.deleteAllProjectRequirementsStmt,
deleteAllScopeMembersStmt: q.deleteAllScopeMembersStmt,
deleteItemStmt: q.deleteItemStmt,
deleteItemForRequirementStmt: q.deleteItemForRequirementStmt,
deleteItemStatProgressStmt: q.deleteItemStatProgressStmt,
deleteProjectStmt: q.deleteProjectStmt,
deleteProjectRequirementStmt: q.deleteProjectRequirementStmt,
deleteProjectRequirementStatStmt: q.deleteProjectRequirementStatStmt,
deleteScopeStmt: q.deleteScopeStmt,
deleteScopeMemberStmt: q.deleteScopeMemberStmt,
deleteStatStmt: q.deleteStatStmt,
getItemStmt: q.getItemStmt,
getItemStatProgressBetweenStmt: q.getItemStatProgressBetweenStmt,
getProjectStmt: q.getProjectStmt,
getScopeStmt: q.getScopeStmt,
getStatStmt: q.getStatStmt,
insertItemStmt: q.insertItemStmt,
insertProjectStmt: q.insertProjectStmt,
insertProjectRequirementStmt: q.insertProjectRequirementStmt,
insertScopeStmt: q.insertScopeStmt,
insertStatStmt: q.insertStatStmt,
listItemStatProgressStmt: q.listItemStatProgressStmt,
listItemStatProgressMultiStmt: q.listItemStatProgressMultiStmt,
listItemsAcquiredBetweenStmt: q.listItemsAcquiredBetweenStmt,
listItemsByProjectStmt: q.listItemsByProjectStmt,
listItemsCreatedBetweenStmt: q.listItemsCreatedBetweenStmt,
listItemsCreatedBetweenNoScopeStmt: q.listItemsCreatedBetweenNoScopeStmt,
listItemsLooseBetweenStmt: q.listItemsLooseBetweenStmt,
listItemsLooseBetweenNoScopeStmt: q.listItemsLooseBetweenNoScopeStmt,
listItemsScheduledBetweenStmt: q.listItemsScheduledBetweenStmt,
listItemsScheduledBetweenNoScopeStmt: q.listItemsScheduledBetweenNoScopeStmt,
listProjectRequirementsStmt: q.listProjectRequirementsStmt,
listProjectRequirementsStatsStmt: q.listProjectRequirementsStatsStmt,
listProjectsStmt: q.listProjectsStmt,
listScopeMembersStmt: q.listScopeMembersStmt,
listScopeMembersMultiStmt: q.listScopeMembersMultiStmt,
listScopesStmt: q.listScopesStmt,
listScopesByUserStmt: q.listScopesByUserStmt,
listStatsStmt: q.listStatsStmt,
replaceItemStatProgressStmt: q.replaceItemStatProgressStmt,
replaceProjectRequirementStatStmt: q.replaceProjectRequirementStatStmt,
replaceScopeMemberStmt: q.replaceScopeMemberStmt,
updateItemStmt: q.updateItemStmt,
updateProjectStmt: q.updateProjectStmt,
updateProjectRequirementStmt: q.updateProjectRequirementStmt,
updateScopeStmt: q.updateScopeStmt,
updateStatStmt: q.updateStatStmt,
}
}

805
ports/mysql/mysqlcore/items.sql.go

@ -0,0 +1,805 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
// source: items.sql
package mysqlcore
import (
"context"
"database/sql"
"time"
"git.aiterp.net/stufflog3/stufflog3/ports/mysql/sqltypes"
)
const clearItemStatProgress = `-- name: ClearItemStatProgress :exec
DELETE FROM item_stat_progress WHERE item_id = ?
`
func (q *Queries) ClearItemStatProgress(ctx context.Context, itemID int) error {
_, err := q.exec(ctx, q.clearItemStatProgressStmt, clearItemStatProgress, itemID)
return err
}
const deleteItem = `-- name: DeleteItem :exec
DELETE FROM item WHERE id = ?
`
func (q *Queries) DeleteItem(ctx context.Context, id int) error {
_, err := q.exec(ctx, q.deleteItemStmt, deleteItem, id)
return err
}
const deleteItemForRequirement = `-- name: DeleteItemForRequirement :exec
DELETE FROM item WHERE project_requirement_id = ?
`
func (q *Queries) DeleteItemForRequirement(ctx context.Context, projectRequirementID sql.NullInt32) error {
_, err := q.exec(ctx, q.deleteItemForRequirementStmt, deleteItemForRequirement, projectRequirementID)
return err
}
const deleteItemStatProgress = `-- name: DeleteItemStatProgress :exec
DELETE FROM item_stat_progress WHERE item_id = ? AND stat_id = ?
`
type DeleteItemStatProgressParams struct {
ItemID int
StatID int
}
func (q *Queries) DeleteItemStatProgress(ctx context.Context, arg DeleteItemStatProgressParams) error {
_, err := q.exec(ctx, q.deleteItemStatProgressStmt, deleteItemStatProgress, arg.ItemID, arg.StatID)
return err
}
const getItem = `-- name: GetItem :one
SELECT i.id, i.scope_id, i.project_requirement_id, i.owner_id, i.name, i.description, i.created_time, i.acquired_time, i.scheduled_date, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE i.id = ?
`
type GetItemRow struct {
ID int
ScopeID int
ProjectRequirementID sql.NullInt32
OwnerID string
Name string
Description string
CreatedTime time.Time
AcquiredTime sql.NullTime
ScheduledDate sqltypes.NullDate
ProjectID sql.NullInt32
}
func (q *Queries) GetItem(ctx context.Context, id int) (GetItemRow, error) {
row := q.queryRow(ctx, q.getItemStmt, getItem, id)
var i GetItemRow
err := row.Scan(
&i.ID,
&i.ScopeID,
&i.ProjectRequirementID,
&i.OwnerID,
&i.Name,
&i.Description,
&i.CreatedTime,
&i.AcquiredTime,
&i.ScheduledDate,
&i.ProjectID,
)
return i, err
}
const getItemStatProgressBetween = `-- name: GetItemStatProgressBetween :one
SELECT s.id, s.name, s.weight, SUM(isp.acquired), SUM(isp.required) FROM item i
LEFT JOIN item_stat_progress isp on i.id = isp.item_id
LEFT JOIN stat s on isp.stat_id = s.id
WHERE i.acquired_time >= ?
AND i.acquired_time < ?
AND i.scope_id = ?
GROUP BY stat_id
`
type GetItemStatProgressBetweenParams struct {
AcquiredTime sql.NullTime
AcquiredTime_2 sql.NullTime
ScopeID int
}
type GetItemStatProgressBetweenRow struct {
ID sql.NullInt32
Name sql.NullString
Weight float64
Sum interface{}
Sum_2 interface{}
}
func (q *Queries) GetItemStatProgressBetween(ctx context.Context, arg GetItemStatProgressBetweenParams) (GetItemStatProgressBetweenRow, error) {
row := q.queryRow(ctx, q.getItemStatProgressBetweenStmt, getItemStatProgressBetween, arg.AcquiredTime, arg.AcquiredTime_2, arg.ScopeID)
var i GetItemStatProgressBetweenRow
err := row.Scan(
&i.ID,
&i.Name,
&i.Weight,
&i.Sum,
&i.Sum_2,
)
return i, err
}
const insertItem = `-- name: InsertItem :execresult
INSERT INTO item (scope_id, project_requirement_id, name, description, created_time, owner_id, acquired_time, scheduled_date)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
`
type InsertItemParams struct {
ScopeID int
ProjectRequirementID sql.NullInt32
Name string
Description string
CreatedTime time.Time
OwnerID string
AcquiredTime sql.NullTime
ScheduledDate sqltypes.NullDate
}
func (q *Queries) InsertItem(ctx context.Context, arg InsertItemParams) (sql.Result, error) {
return q.exec(ctx, q.insertItemStmt, insertItem,
arg.ScopeID,
arg.ProjectRequirementID,
arg.Name,
arg.Description,
arg.CreatedTime,
arg.OwnerID,
arg.AcquiredTime,
arg.ScheduledDate,
)
}
const listItemStatProgress = `-- name: ListItemStatProgress :many
SELECT isp.required, isp.acquired, s.id, s.name, s.weight FROM item_stat_progress isp
LEFT JOIN stat s ON s.id = isp.stat_id
WHERE item_id = ?
`
type ListItemStatProgressRow struct {
Required int
Acquired int
ID sql.NullInt32
Name sql.NullString
Weight float64
}
func (q *Queries) ListItemStatProgress(ctx context.Context, itemID int) ([]ListItemStatProgressRow, error) {
rows, err := q.query(ctx, q.listItemStatProgressStmt, listItemStatProgress, itemID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListItemStatProgressRow{}
for rows.Next() {
var i ListItemStatProgressRow
if err := rows.Scan(
&i.Required,
&i.Acquired,
&i.ID,
&i.Name,
&i.Weight,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listItemStatProgressMulti = `-- name: ListItemStatProgressMulti :many
SELECT isp.item_id, isp.required, isp.acquired, s.id, s.name, s.weight FROM item_stat_progress isp
LEFT JOIN stat s ON s.id = isp.stat_id
WHERE item_id IN (?, ?, ?, ?, ?, ?, ?, ?)
`
type ListItemStatProgressMultiParams struct {
ItemID int
ItemID_2 int
ItemID_3 int
ItemID_4 int
ItemID_5 int
ItemID_6 int
ItemID_7 int
ItemID_8 int
}
type ListItemStatProgressMultiRow struct {
ItemID int
Required int
Acquired int
ID sql.NullInt32
Name sql.NullString
Weight float64
}
func (q *Queries) ListItemStatProgressMulti(ctx context.Context, arg ListItemStatProgressMultiParams) ([]ListItemStatProgressMultiRow, error) {
rows, err := q.query(ctx, q.listItemStatProgressMultiStmt, listItemStatProgressMulti,
arg.ItemID,
arg.ItemID_2,
arg.ItemID_3,
arg.ItemID_4,
arg.ItemID_5,
arg.ItemID_6,
arg.ItemID_7,
arg.ItemID_8,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListItemStatProgressMultiRow{}
for rows.Next() {
var i ListItemStatProgressMultiRow
if err := rows.Scan(
&i.ItemID,
&i.Required,
&i.Acquired,
&i.ID,
&i.Name,
&i.Weight,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listItemsAcquiredBetween = `-- name: ListItemsAcquiredBetween :many
SELECT i.id, i.scope_id, i.project_requirement_id, i.owner_id, i.name, i.description, i.created_time, i.acquired_time, i.scheduled_date, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE i.acquired_time >= ?
AND i.acquired_time <= ?
AND i.scope_id = ?
ORDER BY acquired_time DESC, created_time DESC
`
type ListItemsAcquiredBetweenParams struct {
AcquiredTime sql.NullTime
AcquiredTime_2 sql.NullTime
ScopeID int
}
type ListItemsAcquiredBetweenRow struct {
ID int
ScopeID int
ProjectRequirementID sql.NullInt32
OwnerID string
Name string
Description string
CreatedTime time.Time
AcquiredTime sql.NullTime
ScheduledDate sqltypes.NullDate
ProjectID sql.NullInt32
}
func (q *Queries) ListItemsAcquiredBetween(ctx context.Context, arg ListItemsAcquiredBetweenParams) ([]ListItemsAcquiredBetweenRow, error) {
rows, err := q.query(ctx, q.listItemsAcquiredBetweenStmt, listItemsAcquiredBetween, arg.AcquiredTime, arg.AcquiredTime_2, arg.ScopeID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListItemsAcquiredBetweenRow{}
for rows.Next() {
var i ListItemsAcquiredBetweenRow
if err := rows.Scan(
&i.ID,
&i.ScopeID,
&i.ProjectRequirementID,
&i.OwnerID,
&i.Name,
&i.Description,
&i.CreatedTime,
&i.AcquiredTime,
&i.ScheduledDate,
&i.ProjectID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listItemsByProject = `-- name: ListItemsByProject :many
SELECT i.id, i.scope_id, i.project_requirement_id, i.owner_id, i.name, i.description, i.created_time, i.acquired_time, i.scheduled_date, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE pr.project_id = ?
`
type ListItemsByProjectRow struct {
ID int
ScopeID int
ProjectRequirementID sql.NullInt32
OwnerID string
Name string
Description string
CreatedTime time.Time
AcquiredTime sql.NullTime
ScheduledDate sqltypes.NullDate
ProjectID sql.NullInt32
}
func (q *Queries) ListItemsByProject(ctx context.Context, projectID int) ([]ListItemsByProjectRow, error) {
rows, err := q.query(ctx, q.listItemsByProjectStmt, listItemsByProject, projectID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListItemsByProjectRow{}
for rows.Next() {
var i ListItemsByProjectRow
if err := rows.Scan(
&i.ID,
&i.ScopeID,
&i.ProjectRequirementID,
&i.OwnerID,
&i.Name,
&i.Description,
&i.CreatedTime,
&i.AcquiredTime,
&i.ScheduledDate,
&i.ProjectID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listItemsCreatedBetween = `-- name: ListItemsCreatedBetween :many
SELECT i.id, i.scope_id, i.project_requirement_id, i.owner_id, i.name, i.description, i.created_time, i.acquired_time, i.scheduled_date, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE i.created_time >= ?
AND i.created_time <= ?
AND i.scope_id = ?
ORDER BY created_time DESC
`
type ListItemsCreatedBetweenParams struct {
CreatedTime time.Time
CreatedTime_2 time.Time
ScopeID int
}
type ListItemsCreatedBetweenRow struct {
ID int
ScopeID int
ProjectRequirementID sql.NullInt32
OwnerID string
Name string
Description string
CreatedTime time.Time
AcquiredTime sql.NullTime
ScheduledDate sqltypes.NullDate
ProjectID sql.NullInt32
}
func (q *Queries) ListItemsCreatedBetween(ctx context.Context, arg ListItemsCreatedBetweenParams) ([]ListItemsCreatedBetweenRow, error) {
rows, err := q.query(ctx, q.listItemsCreatedBetweenStmt, listItemsCreatedBetween, arg.CreatedTime, arg.CreatedTime_2, arg.ScopeID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListItemsCreatedBetweenRow{}
for rows.Next() {
var i ListItemsCreatedBetweenRow
if err := rows.Scan(
&i.ID,
&i.ScopeID,
&i.ProjectRequirementID,
&i.OwnerID,
&i.Name,
&i.Description,
&i.CreatedTime,
&i.AcquiredTime,
&i.ScheduledDate,
&i.ProjectID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listItemsCreatedBetweenNoScope = `-- name: ListItemsCreatedBetweenNoScope :many
SELECT i.id, i.scope_id, i.project_requirement_id, i.owner_id, i.name, i.description, i.created_time, i.acquired_time, i.scheduled_date, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE i.created_time >= ?
AND i.created_time < ?
`
type ListItemsCreatedBetweenNoScopeParams struct {
CreatedTime time.Time
CreatedTime_2 time.Time
}
type ListItemsCreatedBetweenNoScopeRow struct {
ID int
ScopeID int
ProjectRequirementID sql.NullInt32
OwnerID string
Name string
Description string
CreatedTime time.Time
AcquiredTime sql.NullTime
ScheduledDate sqltypes.NullDate
ProjectID sql.NullInt32
}
func (q *Queries) ListItemsCreatedBetweenNoScope(ctx context.Context, arg ListItemsCreatedBetweenNoScopeParams) ([]ListItemsCreatedBetweenNoScopeRow, error) {
rows, err := q.query(ctx, q.listItemsCreatedBetweenNoScopeStmt, listItemsCreatedBetweenNoScope, arg.CreatedTime, arg.CreatedTime_2)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListItemsCreatedBetweenNoScopeRow{}
for rows.Next() {
var i ListItemsCreatedBetweenNoScopeRow
if err := rows.Scan(
&i.ID,
&i.ScopeID,
&i.ProjectRequirementID,
&i.OwnerID,
&i.Name,
&i.Description,
&i.CreatedTime,
&i.AcquiredTime,
&i.ScheduledDate,
&i.ProjectID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listItemsLooseBetween = `-- name: ListItemsLooseBetween :many
SELECT i.id, i.scope_id, i.project_requirement_id, i.owner_id, i.name, i.description, i.created_time, i.acquired_time, i.scheduled_date, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE i.created_time >= ?
AND i.created_time <= ?
AND i.scope_id = ?
AND i.scheduled_date IS NULL
AND i.acquired_time IS NULL
AND i.project_requirement_id IS NULL
ORDER BY created_time DESC
`
type ListItemsLooseBetweenParams struct {
CreatedTime time.Time
CreatedTime_2 time.Time
ScopeID int
}
type ListItemsLooseBetweenRow struct {
ID int
ScopeID int
ProjectRequirementID sql.NullInt32
OwnerID string
Name string
Description string
CreatedTime time.Time
AcquiredTime sql.NullTime
ScheduledDate sqltypes.NullDate
ProjectID sql.NullInt32
}
func (q *Queries) ListItemsLooseBetween(ctx context.Context, arg ListItemsLooseBetweenParams) ([]ListItemsLooseBetweenRow, error) {
rows, err := q.query(ctx, q.listItemsLooseBetweenStmt, listItemsLooseBetween, arg.CreatedTime, arg.CreatedTime_2, arg.ScopeID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListItemsLooseBetweenRow{}
for rows.Next() {
var i ListItemsLooseBetweenRow
if err := rows.Scan(
&i.ID,
&i.ScopeID,
&i.ProjectRequirementID,
&i.OwnerID,
&i.Name,
&i.Description,
&i.CreatedTime,
&i.AcquiredTime,
&i.ScheduledDate,
&i.ProjectID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listItemsLooseBetweenNoScope = `-- name: ListItemsLooseBetweenNoScope :many
SELECT i.id, i.scope_id, i.project_requirement_id, i.owner_id, i.name, i.description, i.created_time, i.acquired_time, i.scheduled_date, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE i.created_time >= ?
AND i.created_time < ?
AND i.scheduled_date IS NULL
AND i.acquired_time IS NULL
`
type ListItemsLooseBetweenNoScopeParams struct {
CreatedTime time.Time
CreatedTime_2 time.Time
}
type ListItemsLooseBetweenNoScopeRow struct {
ID int
ScopeID int
ProjectRequirementID sql.NullInt32
OwnerID string
Name string
Description string
CreatedTime time.Time
AcquiredTime sql.NullTime
ScheduledDate sqltypes.NullDate
ProjectID sql.NullInt32
}
func (q *Queries) ListItemsLooseBetweenNoScope(ctx context.Context, arg ListItemsLooseBetweenNoScopeParams) ([]ListItemsLooseBetweenNoScopeRow, error) {
rows, err := q.query(ctx, q.listItemsLooseBetweenNoScopeStmt, listItemsLooseBetweenNoScope, arg.CreatedTime, arg.CreatedTime_2)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListItemsLooseBetweenNoScopeRow{}
for rows.Next() {
var i ListItemsLooseBetweenNoScopeRow
if err := rows.Scan(
&i.ID,
&i.ScopeID,
&i.ProjectRequirementID,
&i.OwnerID,
&i.Name,
&i.Description,
&i.CreatedTime,
&i.AcquiredTime,
&i.ScheduledDate,
&i.ProjectID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listItemsScheduledBetween = `-- name: ListItemsScheduledBetween :many
SELECT i.id, i.scope_id, i.project_requirement_id, i.owner_id, i.name, i.description, i.created_time, i.acquired_time, i.scheduled_date, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE i.scheduled_date >= ?
AND i.scheduled_date <= ?
AND i.scope_id = ?
ORDER BY scheduled_date, created_time
`
type ListItemsScheduledBetweenParams struct {
ScheduledDate sqltypes.NullDate
ScheduledDate_2 sqltypes.NullDate
ScopeID int
}
type ListItemsScheduledBetweenRow struct {
ID int
ScopeID int
ProjectRequirementID sql.NullInt32
OwnerID string
Name string
Description string
CreatedTime time.Time
AcquiredTime sql.NullTime
ScheduledDate sqltypes.NullDate
ProjectID sql.NullInt32
}
func (q *Queries) ListItemsScheduledBetween(ctx context.Context, arg ListItemsScheduledBetweenParams) ([]ListItemsScheduledBetweenRow, error) {
rows, err := q.query(ctx, q.listItemsScheduledBetweenStmt, listItemsScheduledBetween, arg.ScheduledDate, arg.ScheduledDate_2, arg.ScopeID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListItemsScheduledBetweenRow{}
for rows.Next() {
var i ListItemsScheduledBetweenRow
if err := rows.Scan(
&i.ID,
&i.ScopeID,
&i.ProjectRequirementID,
&i.OwnerID,
&i.Name,
&i.Description,
&i.CreatedTime,
&i.AcquiredTime,
&i.ScheduledDate,
&i.ProjectID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listItemsScheduledBetweenNoScope = `-- name: ListItemsScheduledBetweenNoScope :many
SELECT i.id, i.scope_id, i.project_requirement_id, i.owner_id, i.name, i.description, i.created_time, i.acquired_time, i.scheduled_date, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE i.scheduled_date >= ?
AND i.scheduled_date < ?
`
type ListItemsScheduledBetweenNoScopeParams struct {
ScheduledDate sqltypes.NullDate
ScheduledDate_2 sqltypes.NullDate
}
type ListItemsScheduledBetweenNoScopeRow struct {
ID int
ScopeID int
ProjectRequirementID sql.NullInt32
OwnerID string
Name string
Description string
CreatedTime time.Time
AcquiredTime sql.NullTime
ScheduledDate sqltypes.NullDate
ProjectID sql.NullInt32
}
func (q *Queries) ListItemsScheduledBetweenNoScope(ctx context.Context, arg ListItemsScheduledBetweenNoScopeParams) ([]ListItemsScheduledBetweenNoScopeRow, error) {
rows, err := q.query(ctx, q.listItemsScheduledBetweenNoScopeStmt, listItemsScheduledBetweenNoScope, arg.ScheduledDate, arg.ScheduledDate_2)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ListItemsScheduledBetweenNoScopeRow{}
for rows.Next() {
var i ListItemsScheduledBetweenNoScopeRow
if err := rows.Scan(
&i.ID,
&i.ScopeID,
&i.ProjectRequirementID,
&i.OwnerID,
&i.Name,
&i.Description,
&i.CreatedTime,
&i.AcquiredTime,
&i.ScheduledDate,
&i.ProjectID,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const replaceItemStatProgress = `-- name: ReplaceItemStatProgress :exec
REPLACE INTO item_stat_progress (item_id, stat_id, acquired, required)
VALUES (?, ?, ?, ?)
`
type ReplaceItemStatProgressParams struct {
ItemID int
StatID int
Acquired int
Required int
}
func (q *Queries) ReplaceItemStatProgress(ctx context.Context, arg ReplaceItemStatProgressParams) error {
_, err := q.exec(ctx, q.replaceItemStatProgressStmt, replaceItemStatProgress,
arg.ItemID,
arg.StatID,
arg.Acquired,
arg.Required,
)
return err
}
const updateItem = `-- name: UpdateItem :exec
UPDATE item
SET project_requirement_id = ?,
name = ?,
description = ?,
acquired_time = ?,
scheduled_date = ?,
owner_id = ?
WHERE id = ?
`
type UpdateItemParams struct {
ProjectRequirementID sql.NullInt32
Name string
Description string
AcquiredTime sql.NullTime
ScheduledDate sqltypes.NullDate
OwnerID string
ID int
}
func (q *Queries) UpdateItem(ctx context.Context, arg UpdateItemParams) error {
_, err := q.exec(ctx, q.updateItemStmt, updateItem,
arg.ProjectRequirementID,
arg.Name,
arg.Description,
arg.AcquiredTime,
arg.ScheduledDate,
arg.OwnerID,
arg.ID,
)
return err
}

100
ports/mysql/mysqlcore/models.go

@ -0,0 +1,100 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
package mysqlcore
import (
"database/sql"
"time"
"git.aiterp.net/stufflog3/stufflog3/ports/mysql/sqltypes"
)
type Item struct {
ID int
ScopeID int
ProjectRequirementID sql.NullInt32
OwnerID string
Name string
Description string
CreatedTime time.Time
AcquiredTime sql.NullTime
ScheduledDate sqltypes.NullDate
}
type ItemStatProgress struct {
ItemID int
StatID int
Acquired int
Required int
}
type Project struct {
ID int
ScopeID int
OwnerID string
Name string
Status int
Description string
CreatedTime time.Time
}
type ProjectRequirement struct {
ID int
ScopeID int
ProjectID int
Name string
Status int
Description string
}
type ProjectRequirementStat struct {
ProjectRequirementID int
StatID int
Required int
}
type Scope struct {
ID int
Name string
Abbreviation string
CustomLabels sqltypes.NullRawMessage
}
type ScopeMember struct {
ScopeID int
UserID string
Name string
Owner bool
}
type Sprint struct {
ID int
ScopeID int
Name string
Description string
FromTime time.Time
ToTime time.Time
IsTimed bool
IsCoarse bool
IsUnweighted bool
Kind int
AggregateName string
AggregateRequired int
}
type SprintPart struct {
SprintID int
ObjectID int
Required int
}
type Stat struct {
ID int
ScopeID int
Name string
Description string
Weight float64
AllowedAmounts sqltypes.NullRawMessage
}

323
ports/mysql/mysqlcore/projects.sql.go

@ -0,0 +1,323 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
// source: projects.sql
package mysqlcore
import (
"context"
"database/sql"
)
const clearItemProjectRequirement = `-- name: ClearItemProjectRequirement :exec
UPDATE item
SET project_requirement_id = NULL
WHERE project_requirement_id = ?
`
func (q *Queries) ClearItemProjectRequirement(ctx context.Context, projectRequirementID sql.NullInt32) error {
_, err := q.exec(ctx, q.clearItemProjectRequirementStmt, clearItemProjectRequirement, projectRequirementID)
return err
}
const deleteAllProjectRequirementStats = `-- name: DeleteAllProjectRequirementStats :exec
DELETE FROM project_requirement_stat WHERE project_requirement_id = ?
`
func (q *Queries) DeleteAllProjectRequirementStats(ctx context.Context, projectRequirementID int) error {
_, err := q.exec(ctx, q.deleteAllProjectRequirementStatsStmt, deleteAllProjectRequirementStats, projectRequirementID)
return err
}
const deleteAllProjectRequirements = `-- name: DeleteAllProjectRequirements :exec
DELETE FROM project_requirement WHERE project_id = ?
`
func (q *Queries) DeleteAllProjectRequirements(ctx context.Context, projectID int) error {
_, err := q.exec(ctx, q.deleteAllProjectRequirementsStmt, deleteAllProjectRequirements, projectID)
return err
}
const deleteProject = `-- name: DeleteProject :exec
DELETE FROM project WHERE id = ? AND scope_id = ?
`
type DeleteProjectParams struct {
ID int
ScopeID int
}
func (q *Queries) DeleteProject(ctx context.Context, arg DeleteProjectParams) error {
_, err := q.exec(ctx, q.deleteProjectStmt, deleteProject, arg.ID, arg.ScopeID)
return err
}
const deleteProjectRequirement = `-- name: DeleteProjectRequirement :exec
DELETE FROM project_requirement WHERE id = ? AND scope_id = ?
`
type DeleteProjectRequirementParams struct {
ID int
ScopeID int
}
func (q *Queries) DeleteProjectRequirement(ctx context.Context, arg DeleteProjectRequirementParams) error {
_, err := q.exec(ctx, q.deleteProjectRequirementStmt, deleteProjectRequirement, arg.ID, arg.ScopeID)
return err
}
const deleteProjectRequirementStat = `-- name: DeleteProjectRequirementStat :exec
DELETE FROM project_requirement_stat WHERE project_requirement_id = ? AND stat_id = ?
`
type DeleteProjectRequirementStatParams struct {
ProjectRequirementID int
StatID int
}
func (q *Queries) DeleteProjectRequirementStat(ctx context.Context, arg DeleteProjectRequirementStatParams) error {
_, err := q.exec(ctx, q.deleteProjectRequirementStatStmt, deleteProjectRequirementStat, arg.ProjectRequirementID, arg.StatID)
return err
}
const getProject = `-- name: GetProject :one
SELECT id, scope_id, owner_id, name, status, description, created_time FROM project WHERE id = ? AND scope_id = ?
`
type GetProjectParams struct {
ID int
ScopeID int
}
func (q *Queries) GetProject(ctx context.Context, arg GetProjectParams) (Project, error) {
row := q.queryRow(ctx, q.getProjectStmt, getProject, arg.ID, arg.ScopeID)
var i Project
err := row.Scan(
&i.ID,
&i.ScopeID,
&i.OwnerID,
&i.Name,
&i.Status,
&i.Description,
&i.CreatedTime,
)
return i, err
}
const insertProject = `-- name: InsertProject :execresult
INSERT INTO project (scope_id, owner_id, name, status, description)
VALUES (?, ?, ?, ?, ?)
`
type InsertProjectParams struct {
ScopeID int
OwnerID string
Name string
Status int
Description string
}
func (q *Queries) InsertProject(ctx context.Context, arg InsertProjectParams) (sql.Result, error) {
return q.exec(ctx, q.insertProjectStmt, insertProject,
arg.ScopeID,
arg.OwnerID,
arg.Name,
arg.Status,
arg.Description,
)
}
const insertProjectRequirement = `-- name: InsertProjectRequirement :execresult
INSERT INTO project_requirement (scope_id, project_id, name, status, description)
VALUES (?, ?, ?, ?, ?)
`
type InsertProjectRequirementParams struct {
ScopeID int
ProjectID int
Name string
Status int
Description string
}
func (q *Queries) InsertProjectRequirement(ctx context.Context, arg InsertProjectRequirementParams) (sql.Result, error) {
return q.exec(ctx, q.insertProjectRequirementStmt, insertProjectRequirement,
arg.ScopeID,
arg.ProjectID,
arg.Name,
arg.Status,
arg.Description,
)
}
const listProjectRequirements = `-- name: ListProjectRequirements :many
SELECT id, scope_id, project_id, name, status, description FROM project_requirement WHERE project_id = ?
`
func (q *Queries) ListProjectRequirements(ctx context.Context, projectID int) ([]ProjectRequirement, error) {
rows, err := q.query(ctx, q.listProjectRequirementsStmt, listProjectRequirements, projectID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ProjectRequirement{}
for rows.Next() {
var i ProjectRequirement
if err := rows.Scan(
&i.ID,
&i.ScopeID,
&i.ProjectID,
&i.Name,
&i.Status,
&i.Description,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listProjectRequirementsStats = `-- name: ListProjectRequirementsStats :many
SELECT prs.project_requirement_id, prs.stat_id, prs.required FROM project_requirement pr
RIGHT JOIN project_requirement_stat prs ON pr.id = prs.project_requirement_id
WHERE pr.project_id = ?
`
func (q *Queries) ListProjectRequirementsStats(ctx context.Context, projectID int) ([]ProjectRequirementStat, error) {
rows, err := q.query(ctx, q.listProjectRequirementsStatsStmt, listProjectRequirementsStats, projectID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ProjectRequirementStat{}
for rows.Next() {
var i ProjectRequirementStat
if err := rows.Scan(&i.ProjectRequirementID, &i.StatID, &i.Required); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listProjects = `-- name: ListProjects :many
SELECT id, scope_id, owner_id, name, status, description, created_time FROM project WHERE scope_id = ? ORDER BY status, name
`
func (q *Queries) ListProjects(ctx context.Context, scopeID int) ([]Project, error) {
rows, err := q.query(ctx, q.listProjectsStmt, listProjects, scopeID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Project{}
for rows.Next() {
var i Project
if err := rows.Scan(
&i.ID,
&i.ScopeID,
&i.OwnerID,
&i.Name,
&i.Status,
&i.Description,
&i.CreatedTime,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const replaceProjectRequirementStat = `-- name: ReplaceProjectRequirementStat :exec
REPLACE INTO project_requirement_stat (project_requirement_id, stat_id, required)
VALUES (?, ?, ?)
`
type ReplaceProjectRequirementStatParams struct {
ProjectRequirementID int
StatID int
Required int
}
func (q *Queries) ReplaceProjectRequirementStat(ctx context.Context, arg ReplaceProjectRequirementStatParams) error {
_, err := q.exec(ctx, q.replaceProjectRequirementStatStmt, replaceProjectRequirementStat, arg.ProjectRequirementID, arg.StatID, arg.Required)
return err
}
const updateProject = `-- name: UpdateProject :exec
UPDATE project
SET owner_id = ?,
name = ?,
status = ?,
description = ?
WHERE id = ? AND scope_id = ?
`
type UpdateProjectParams struct {
OwnerID string
Name string
Status int
Description string
ID int
ScopeID int
}
func (q *Queries) UpdateProject(ctx context.Context, arg UpdateProjectParams) error {
_, err := q.exec(ctx, q.updateProjectStmt, updateProject,
arg.OwnerID,
arg.Name,
arg.Status,
arg.Description,
arg.ID,
arg.ScopeID,
)
return err
}
const updateProjectRequirement = `-- name: UpdateProjectRequirement :exec
UPDATE project_requirement
SET name = ?,
status = ?,
description = ?
WHERE id = ? AND scope_id = ?
`
type UpdateProjectRequirementParams struct {
Name string
Status int
Description string
ID int
ScopeID int
}
func (q *Queries) UpdateProjectRequirement(ctx context.Context, arg UpdateProjectRequirementParams) error {
_, err := q.exec(ctx, q.updateProjectRequirementStmt, updateProjectRequirement,
arg.Name,
arg.Status,
arg.Description,
arg.ID,
arg.ScopeID,
)
return err
}

278
ports/mysql/mysqlcore/scopes.sql.go

@ -0,0 +1,278 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
// source: scopes.sql
package mysqlcore
import (
"context"
"database/sql"
"git.aiterp.net/stufflog3/stufflog3/ports/mysql/sqltypes"
)
const deleteAllScopeMembers = `-- name: DeleteAllScopeMembers :exec
DELETE FROM scope_member WHERE scope_id=?
`
func (q *Queries) DeleteAllScopeMembers(ctx context.Context, scopeID int) error {
_, err := q.exec(ctx, q.deleteAllScopeMembersStmt, deleteAllScopeMembers, scopeID)
return err
}
const deleteScope = `-- name: DeleteScope :exec
DELETE FROM scope WHERE id=?
`
func (q *Queries) DeleteScope(ctx context.Context, id int) error {
_, err := q.exec(ctx, q.deleteScopeStmt, deleteScope, id)
return err
}
const deleteScopeMember = `-- name: DeleteScopeMember :exec
DELETE FROM scope_member WHERE scope_id=? AND user_id=?
`
type DeleteScopeMemberParams struct {
ScopeID int
UserID string
}
func (q *Queries) DeleteScopeMember(ctx context.Context, arg DeleteScopeMemberParams) error {
_, err := q.exec(ctx, q.deleteScopeMemberStmt, deleteScopeMember, arg.ScopeID, arg.UserID)
return err
}
const getScope = `-- name: GetScope :one
SELECT id, name, abbreviation, custom_labels FROM scope
WHERE id = ?
`
func (q *Queries) GetScope(ctx context.Context, id int) (Scope, error) {
row := q.queryRow(ctx, q.getScopeStmt, getScope, id)
var i Scope
err := row.Scan(
&i.ID,
&i.Name,
&i.Abbreviation,
&i.CustomLabels,
)
return i, err
}
const insertScope = `-- name: InsertScope :execresult
INSERT INTO scope (id, name, abbreviation, custom_labels)
VALUES (?, ?, ?, ?)
`
type InsertScopeParams struct {
ID int
Name string
Abbreviation string
CustomLabels sqltypes.NullRawMessage
}
func (q *Queries) InsertScope(ctx context.Context, arg InsertScopeParams) (sql.Result, error) {
return q.exec(ctx, q.insertScopeStmt, insertScope,
arg.ID,
arg.Name,
arg.Abbreviation,
arg.CustomLabels,
)
}
const listScopeMembers = `-- name: ListScopeMembers :many
SELECT scope_id, user_id, name, owner FROM scope_member
WHERE scope_id = ?
ORDER BY name
`
func (q *Queries) ListScopeMembers(ctx context.Context, scopeID int) ([]ScopeMember, error) {
rows, err := q.query(ctx, q.listScopeMembersStmt, listScopeMembers, scopeID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ScopeMember{}
for rows.Next() {
var i ScopeMember
if err := rows.Scan(
&i.ScopeID,
&i.UserID,
&i.Name,
&i.Owner,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listScopeMembersMulti = `-- name: ListScopeMembersMulti :many
SELECT scope_id, user_id, name, owner FROM scope_member
WHERE scope_id IN (?, ?, ?, ?, ?, ?)
ORDER BY name
`
type ListScopeMembersMultiParams struct {
ScopeID int
ScopeID_2 int
ScopeID_3 int
ScopeID_4 int
ScopeID_5 int
ScopeID_6 int
}
func (q *Queries) ListScopeMembersMulti(ctx context.Context, arg ListScopeMembersMultiParams) ([]ScopeMember, error) {
rows, err := q.query(ctx, q.listScopeMembersMultiStmt, listScopeMembersMulti,
arg.ScopeID,
arg.ScopeID_2,
arg.ScopeID_3,
arg.ScopeID_4,
arg.ScopeID_5,
arg.ScopeID_6,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ScopeMember{}
for rows.Next() {
var i ScopeMember
if err := rows.Scan(
&i.ScopeID,
&i.UserID,
&i.Name,
&i.Owner,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listScopes = `-- name: ListScopes :many
SELECT id, name, abbreviation, custom_labels FROM scope
ORDER BY name
`
func (q *Queries) ListScopes(ctx context.Context) ([]Scope, error) {
rows, err := q.query(ctx, q.listScopesStmt, listScopes)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Scope{}
for rows.Next() {
var i Scope
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Abbreviation,
&i.CustomLabels,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listScopesByUser = `-- name: ListScopesByUser :many
SELECT s.id, s.name, s.abbreviation, s.custom_labels FROM scope_member sm
RIGHT JOIN scope s on s.id = sm.scope_id
WHERE sm.user_id = ?
ORDER BY s.name
`
func (q *Queries) ListScopesByUser(ctx context.Context, userID string) ([]Scope, error) {
rows, err := q.query(ctx, q.listScopesByUserStmt, listScopesByUser, userID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Scope{}
for rows.Next() {
var i Scope
if err := rows.Scan(
&i.ID,
&i.Name,
&i.Abbreviation,
&i.CustomLabels,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const replaceScopeMember = `-- name: ReplaceScopeMember :exec
REPLACE INTO scope_member (scope_id, user_id, name, owner)
VALUES (?, ?, ?, ?)
`
type ReplaceScopeMemberParams struct {
ScopeID int
UserID string
Name string
Owner bool
}
func (q *Queries) ReplaceScopeMember(ctx context.Context, arg ReplaceScopeMemberParams) error {
_, err := q.exec(ctx, q.replaceScopeMemberStmt, replaceScopeMember,
arg.ScopeID,
arg.UserID,
arg.Name,
arg.Owner,
)
return err
}
const updateScope = `-- name: UpdateScope :exec
UPDATE scope SET name = ?, abbreviation = ?, custom_labels = ? WHERE id = ?
`
type UpdateScopeParams struct {
Name string
Abbreviation string
CustomLabels sqltypes.NullRawMessage
ID int
}
func (q *Queries) UpdateScope(ctx context.Context, arg UpdateScopeParams) error {
_, err := q.exec(ctx, q.updateScopeStmt, updateScope,
arg.Name,
arg.Abbreviation,
arg.CustomLabels,
arg.ID,
)
return err
}

155
ports/mysql/mysqlcore/stats.sql.go

@ -0,0 +1,155 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.13.0
// source: stats.sql
package mysqlcore
import (
"context"
"database/sql"
"git.aiterp.net/stufflog3/stufflog3/ports/mysql/sqltypes"
)
const deleteAllItemStatProgressByStatId = `-- name: DeleteAllItemStatProgressByStatId :exec
DELETE FROM item_stat_progress WHERE stat_id = ?
`
func (q *Queries) DeleteAllItemStatProgressByStatId(ctx context.Context, statID int) error {
_, err := q.exec(ctx, q.deleteAllItemStatProgressByStatIdStmt, deleteAllItemStatProgressByStatId, statID)
return err
}
const deleteAllProjectRequirementStatByStatId = `-- name: DeleteAllProjectRequirementStatByStatId :exec
DELETE FROM project_requirement_stat WHERE stat_id = ?
`
func (q *Queries) DeleteAllProjectRequirementStatByStatId(ctx context.Context, statID int) error {
_, err := q.exec(ctx, q.deleteAllProjectRequirementStatByStatIdStmt, deleteAllProjectRequirementStatByStatId, statID)
return err
}
const deleteStat = `-- name: DeleteStat :exec
DELETE FROM stat WHERE id = ? AND scope_id = ?
`
type DeleteStatParams struct {
ID int
ScopeID int
}
func (q *Queries) DeleteStat(ctx context.Context, arg DeleteStatParams) error {
_, err := q.exec(ctx, q.deleteStatStmt, deleteStat, arg.ID, arg.ScopeID)
return err
}
const getStat = `-- name: GetStat :one
SELECT id, scope_id, name, description, weight, allowed_amounts FROM stat WHERE id = ? AND scope_id = ?
`
type GetStatParams struct {
ID int
ScopeID int
}
func (q *Queries) GetStat(ctx context.Context, arg GetStatParams) (Stat, error) {
row := q.queryRow(ctx, q.getStatStmt, getStat, arg.ID, arg.ScopeID)
var i Stat
err := row.Scan(
&i.ID,
&i.ScopeID,
&i.Name,
&i.Description,
&i.Weight,
&i.AllowedAmounts,
)
return i, err
}
const insertStat = `-- name: InsertStat :execresult
INSERT INTO stat (scope_id, name, description, weight, allowed_amounts)
VALUES (?, ?, ?, ?, ?)
`
type InsertStatParams struct {
ScopeID int
Name string
Description string
Weight float64
AllowedAmounts sqltypes.NullRawMessage
}
func (q *Queries) InsertStat(ctx context.Context, arg InsertStatParams) (sql.Result, error) {
return q.exec(ctx, q.insertStatStmt, insertStat,
arg.ScopeID,
arg.Name,
arg.Description,
arg.Weight,
arg.AllowedAmounts,
)
}
const listStats = `-- name: ListStats :many
SELECT id, scope_id, name, description, weight, allowed_amounts FROM stat WHERE scope_id = ? ORDER BY name
`
func (q *Queries) ListStats(ctx context.Context, scopeID int) ([]Stat, error) {
rows, err := q.query(ctx, q.listStatsStmt, listStats, scopeID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []Stat{}
for rows.Next() {
var i Stat
if err := rows.Scan(
&i.ID,
&i.ScopeID,
&i.Name,
&i.Description,
&i.Weight,
&i.AllowedAmounts,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const updateStat = `-- name: UpdateStat :exec
UPDATE stat
SET name = ?,
description = ?,
weight = ?,
allowed_amounts = ?
WHERE id = ? AND scope_id = ?
`
type UpdateStatParams struct {
Name string
Description string
Weight float64
AllowedAmounts sqltypes.NullRawMessage
ID int
ScopeID int
}
func (q *Queries) UpdateStat(ctx context.Context, arg UpdateStatParams) error {
_, err := q.exec(ctx, q.updateStatStmt, updateStat,
arg.Name,
arg.Description,
arg.Weight,
arg.AllowedAmounts,
arg.ID,
arg.ScopeID,
)
return err
}

246
ports/mysql/projects.go

@ -0,0 +1,246 @@
package mysql
import (
"context"
"database/sql"
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/models"
"git.aiterp.net/stufflog3/stufflog3/ports/mysql/mysqlcore"
)
type projectRepository struct {
db *sql.DB
q *mysqlcore.Queries
}
func (r *projectRepository) Find(ctx context.Context, scopeID, projectID int) (*entities.Project, error) {
row, err := r.q.GetProject(ctx, mysqlcore.GetProjectParams{ID: projectID, ScopeID: scopeID})
if err != nil {
if err == sql.ErrNoRows {
return nil, models.NotFoundError("Project")
}
return nil, err
}
return &entities.Project{
ID: row.ID,
ScopeID: row.ScopeID,
OwnerID: row.OwnerID,
CreatedTime: row.CreatedTime,
Name: row.Name,
Description: row.Description,
Status: models.Status(row.Status),
}, nil
}
func (r *projectRepository) List(ctx context.Context, scopeID int) ([]entities.Project, error) {
rows, err := r.q.ListProjects(ctx, scopeID)
if err != nil {
if err == sql.ErrNoRows {
return []entities.Project{}, nil
}
return nil, err
}
res := make([]entities.Project, 0, len(rows))
for _, row := range rows {
res = append(res, entities.Project{
ID: row.ID,
ScopeID: row.ScopeID,
OwnerID: row.OwnerID,
CreatedTime: row.CreatedTime,
Name: row.Name,
Description: row.Description,
Status: models.Status(row.Status),
})
}
return res, nil
}
func (r *projectRepository) Create(ctx context.Context, project entities.Project) (*entities.Project, error) {
res, err := r.q.InsertProject(ctx, mysqlcore.InsertProjectParams{
ScopeID: project.ScopeID,
OwnerID: project.OwnerID,
Name: project.Name,
Status: int(project.Status),
Description: project.Description,
})
if err != nil {
return nil, err
}
id, err := res.LastInsertId()
if err != nil {
return nil, err
}
project.ID = int(id)
return &project, nil
}
func (r *projectRepository) Update(ctx context.Context, project entities.Project, update models.ProjectUpdate) error {
project.Update(update)
return r.q.UpdateProject(ctx, mysqlcore.UpdateProjectParams{
OwnerID: project.OwnerID,
Name: project.Name,
Status: int(project.Status),
Description: project.Description,
ID: project.ID,
ScopeID: project.ScopeID,
})
}
func (r *projectRepository) Delete(ctx context.Context, project entities.Project) error {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
q := r.q.WithTx(tx)
reqs, err := q.ListProjectRequirements(ctx, project.ID)
if err != nil {
return err
}
for _, req := range reqs {
err = q.ClearItemProjectRequirement(ctx, sql.NullInt32{Valid: true, Int32: int32(req.ID)})
if err != nil {
return err
}
err = q.DeleteAllProjectRequirementStats(ctx, req.ID)
if err != nil {
return err
}
}
err = q.DeleteAllProjectRequirements(ctx, project.ID)
if err != nil {
return err
}
err = q.DeleteProject(ctx, mysqlcore.DeleteProjectParams{ID: project.ID, ScopeID: project.ScopeID})
if err != nil {
return err
}
return tx.Commit()
}
func (r *projectRepository) ListRequirements(ctx context.Context, projectID int) ([]entities.Requirement, []entities.RequirementStat, error) {
reqRows, err := r.q.ListProjectRequirements(ctx, projectID)
if err != nil && err != sql.ErrNoRows {
return nil, nil, err
}
statsRows, err := r.q.ListProjectRequirementsStats(ctx, projectID)
if err != nil && err != sql.ErrNoRows {
return nil, nil, err
}
requirements := make([]entities.Requirement, 0, len(reqRows))
for _, row := range reqRows {
requirements = append(requirements, entities.Requirement{
ID: row.ID,
ScopeID: row.ScopeID,
ProjectID: row.ProjectID,
Name: row.Name,
Description: row.Description,
Status: models.Status(row.Status),
})
}
stats := make([]entities.RequirementStat, 0, len(statsRows))
for _, row := range statsRows {
stats = append(stats, entities.RequirementStat{
RequirementID: row.ProjectRequirementID,
StatID: row.StatID,
Required: row.Required,
})
}
return requirements, stats, nil
}
func (r *projectRepository) CreateRequirement(ctx context.Context, requirement entities.Requirement) (*entities.Requirement, error) {
res, err := r.q.InsertProjectRequirement(ctx, mysqlcore.InsertProjectRequirementParams{
ScopeID: requirement.ScopeID,
ProjectID: requirement.ProjectID,
Name: requirement.Name,
Status: int(requirement.Status),
Description: requirement.Description,
})
if err != nil {
return nil, err
}
id, err := res.LastInsertId()
if err != nil {
return nil, err
}
requirement.ID = int(id)
return &requirement, nil
}
func (r *projectRepository) UpdateRequirement(ctx context.Context, requirement entities.Requirement, update models.RequirementUpdate) error {
requirement.Update(update)
return r.q.UpdateProjectRequirement(ctx, mysqlcore.UpdateProjectRequirementParams{
Name: requirement.Name,
Status: int(requirement.Status),
Description: requirement.Description,
ID: requirement.ID,
ScopeID: requirement.ScopeID,
})
}
func (r *projectRepository) DeleteRequirement(ctx context.Context, requirement entities.Requirement) error {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
q := r.q.WithTx(tx)
err = q.ClearItemProjectRequirement(ctx, sql.NullInt32{Valid: true, Int32: int32(requirement.ID)})
if err != nil {
return err
}
err = q.DeleteAllProjectRequirementStats(ctx, requirement.ID)
if err != nil {
return err
}
err = q.DeleteProjectRequirement(ctx, mysqlcore.DeleteProjectRequirementParams{
ScopeID: requirement.ScopeID,
ID: requirement.ID,
})
if err != nil {
return err
}
return tx.Commit()
}
func (r *projectRepository) UpsertRequirementStat(ctx context.Context, stat entities.RequirementStat) error {
return r.q.ReplaceProjectRequirementStat(ctx, mysqlcore.ReplaceProjectRequirementStatParams{
ProjectRequirementID: stat.RequirementID,
StatID: stat.StatID,
Required: stat.Required,
})
}
func (r *projectRepository) DeleteRequirementStat(ctx context.Context, stat entities.RequirementStat) error {
return r.q.DeleteProjectRequirementStat(ctx, mysqlcore.DeleteProjectRequirementStatParams{
ProjectRequirementID: stat.RequirementID,
StatID: stat.StatID,
})
}

114
ports/mysql/queries/items.sql

@ -0,0 +1,114 @@
-- name: GetItem :one
SELECT i.*, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE i.id = ?;
-- name: ListItemsByProject :many
SELECT i.*, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE pr.project_id = ?;
-- name: ListItemsAcquiredBetween :many
SELECT i.*, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE i.acquired_time >= ?
AND i.acquired_time <= ?
AND i.scope_id = ?
ORDER BY acquired_time DESC, created_time DESC;
-- name: ListItemsScheduledBetween :many
SELECT i.*, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE i.scheduled_date >= ?
AND i.scheduled_date <= ?
AND i.scope_id = ?
ORDER BY scheduled_date, created_time;
-- name: ListItemsCreatedBetween :many
SELECT i.*, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE i.created_time >= ?
AND i.created_time <= ?
AND i.scope_id = ?
ORDER BY created_time DESC;
-- name: ListItemsLooseBetween :many
SELECT i.*, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE i.created_time >= ?
AND i.created_time <= ?
AND i.scope_id = ?
AND i.scheduled_date IS NULL
AND i.acquired_time IS NULL
AND i.project_requirement_id IS NULL
ORDER BY created_time DESC;
-- name: ListItemsScheduledBetweenNoScope :many
SELECT i.*, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE i.scheduled_date >= ?
AND i.scheduled_date < ?;
-- name: ListItemsCreatedBetweenNoScope :many
SELECT i.*, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE i.created_time >= ?
AND i.created_time < ?;
-- name: ListItemsLooseBetweenNoScope :many
SELECT i.*, pr.project_id FROM item i
LEFT JOIN project_requirement pr ON pr.id = i.project_requirement_id
WHERE i.created_time >= ?
AND i.created_time < ?
AND i.scheduled_date IS NULL
AND i.acquired_time IS NULL;
-- name: GetItemStatProgressBetween :one
SELECT s.id, s.name, s.weight, SUM(isp.acquired), SUM(isp.required) FROM item i
LEFT JOIN item_stat_progress isp on i.id = isp.item_id
LEFT JOIN stat s on isp.stat_id = s.id
WHERE i.acquired_time >= ?
AND i.acquired_time < ?
AND i.scope_id = ?
GROUP BY stat_id;
-- name: ListItemStatProgress :many
SELECT isp.required, isp.acquired, s.id, s.name, s.weight FROM item_stat_progress isp
LEFT JOIN stat s ON s.id = isp.stat_id
WHERE item_id = ?;
-- name: ListItemStatProgressMulti :many
SELECT isp.item_id, isp.required, isp.acquired, s.id, s.name, s.weight FROM item_stat_progress isp
LEFT JOIN stat s ON s.id = isp.stat_id
WHERE item_id IN (?, ?, ?, ?, ?, ?, ?, ?);
-- name: InsertItem :execresult
INSERT INTO item (scope_id, project_requirement_id, name, description, created_time, owner_id, acquired_time, scheduled_date)
VALUES (?, ?, ?, ?, ?, ?, ?, ?);
-- name: UpdateItem :exec
UPDATE item
SET project_requirement_id = ?,
name = ?,
description = ?,
acquired_time = ?,
scheduled_date = ?,
owner_id = ?
WHERE id = ?;
-- name: DeleteItem :exec
DELETE FROM item WHERE id = ?;
-- name: DeleteItemForRequirement :exec
DELETE FROM item WHERE project_requirement_id = ?;
-- name: ReplaceItemStatProgress :exec
REPLACE INTO item_stat_progress (item_id, stat_id, acquired, required)
VALUES (?, ?, ?, ?);
-- name: DeleteItemStatProgress :exec
DELETE FROM item_stat_progress WHERE item_id = ? AND stat_id = ?;
-- name: ClearItemStatProgress :exec
DELETE FROM item_stat_progress WHERE item_id = ?;

60
ports/mysql/queries/projects.sql

@ -0,0 +1,60 @@
-- name: ListProjects :many
SELECT * FROM project WHERE scope_id = ? ORDER BY status, name;
-- name: GetProject :one
SELECT * FROM project WHERE id = ? AND scope_id = ?;
-- name: InsertProject :execresult
INSERT INTO project (scope_id, owner_id, name, status, description)
VALUES (?, ?, ?, ?, ?);
-- name: UpdateProject :exec
UPDATE project
SET owner_id = ?,
name = ?,
status = ?,
description = ?
WHERE id = ? AND scope_id = ?;
-- name: DeleteProject :exec
DELETE FROM project WHERE id = ? AND scope_id = ?;
-- name: ListProjectRequirements :many
SELECT * FROM project_requirement WHERE project_id = ?;
-- name: InsertProjectRequirement :execresult
INSERT INTO project_requirement (scope_id, project_id, name, status, description)
VALUES (?, ?, ?, ?, ?);
-- name: UpdateProjectRequirement :exec
UPDATE project_requirement
SET name = ?,
status = ?,
description = ?
WHERE id = ? AND scope_id = ?;
-- name: DeleteProjectRequirement :exec
DELETE FROM project_requirement WHERE id = ? AND scope_id = ?;
-- name: DeleteAllProjectRequirements :exec
DELETE FROM project_requirement WHERE project_id = ?;
-- name: ClearItemProjectRequirement :exec
UPDATE item
SET project_requirement_id = NULL
WHERE project_requirement_id = ?;
-- name: ListProjectRequirementsStats :many
SELECT prs.* FROM project_requirement pr
RIGHT JOIN project_requirement_stat prs ON pr.id = prs.project_requirement_id
WHERE pr.project_id = ?;
-- name: ReplaceProjectRequirementStat :exec
REPLACE INTO project_requirement_stat (project_requirement_id, stat_id, required)
VALUES (?, ?, ?);
-- name: DeleteProjectRequirementStat :exec
DELETE FROM project_requirement_stat WHERE project_requirement_id = ? AND stat_id = ?;
-- name: DeleteAllProjectRequirementStats :exec
DELETE FROM project_requirement_stat WHERE project_requirement_id = ?;

43
ports/mysql/queries/scopes.sql

@ -0,0 +1,43 @@
-- name: GetScope :one
SELECT * FROM scope
WHERE id = ?;
-- name: ListScopes :many
SELECT * FROM scope
ORDER BY name;
-- name: ListScopesByUser :many
SELECT s.* FROM scope_member sm
RIGHT JOIN scope s on s.id = sm.scope_id
WHERE sm.user_id = ?
ORDER BY s.name;
-- name: ListScopeMembers :many
SELECT * FROM scope_member
WHERE scope_id = ?
ORDER BY name;
-- name: ListScopeMembersMulti :many
SELECT * FROM scope_member
WHERE scope_id IN (?, ?, ?, ?, ?, ?)
ORDER BY name;
-- name: InsertScope :execresult
INSERT INTO scope (id, name, abbreviation, custom_labels)
VALUES (?, ?, ?, ?);
-- name: UpdateScope :exec
UPDATE scope SET name = ?, abbreviation = ?, custom_labels = ? WHERE id = ?;
-- name: ReplaceScopeMember :exec
REPLACE INTO scope_member (scope_id, user_id, name, owner)
VALUES (?, ?, ?, ?);
-- name: DeleteScopeMember :exec
DELETE FROM scope_member WHERE scope_id=? AND user_id=?;
-- name: DeleteAllScopeMembers :exec
DELETE FROM scope_member WHERE scope_id=?;
-- name: DeleteScope :exec
DELETE FROM scope WHERE id=?;

26
ports/mysql/queries/stats.sql

@ -0,0 +1,26 @@
-- name: GetStat :one
SELECT * FROM stat WHERE id = ? AND scope_id = ?;
-- name: ListStats :many
SELECT * FROM stat WHERE scope_id = ? ORDER BY name;
-- name: InsertStat :execresult
INSERT INTO stat (scope_id, name, description, weight, allowed_amounts)
VALUES (?, ?, ?, ?, ?);
-- name: UpdateStat :exec
UPDATE stat
SET name = ?,
description = ?,
weight = ?,
allowed_amounts = ?
WHERE id = ? AND scope_id = ?;
-- name: DeleteStat :exec
DELETE FROM stat WHERE id = ? AND scope_id = ?;
-- name: DeleteAllItemStatProgressByStatId :exec
DELETE FROM item_stat_progress WHERE stat_id = ?;
-- name: DeleteAllProjectRequirementStatByStatId :exec
DELETE FROM project_requirement_stat WHERE stat_id = ?;

197
ports/mysql/scopes.go

@ -0,0 +1,197 @@
package mysql
import (
"context"
"database/sql"
"encoding/json"
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/models"
"git.aiterp.net/stufflog3/stufflog3/ports/mysql/mysqlcore"
)
type scopeRepository struct {
db *sql.DB
q *mysqlcore.Queries
}
func (r *scopeRepository) Find(ctx context.Context, id int) (*entities.Scope, error) {
row, err := r.q.GetScope(ctx, id)
if err != nil {
if err == sql.ErrNoRows {
return nil, models.NotFoundError("Scope")
}
return nil, err
}
customLabels := make(map[string]string, 0)
if row.CustomLabels.Valid {
_ = json.Unmarshal(row.CustomLabels.RawMessage, &customLabels)
}
return &entities.Scope{
ID: row.ID,
Name: row.Name,
Abbreviation: row.Abbreviation,
CustomLabels: customLabels,
}, nil
}
func (r *scopeRepository) List(ctx context.Context) ([]entities.Scope, error) {
rows, err := r.q.ListScopes(ctx)
if err != nil {
if err == sql.ErrNoRows {
return []entities.Scope{}, nil
}
return nil, err
}
res := make([]entities.Scope, 0, len(rows))
for _, row := range rows {
customLabels := make(map[string]string, 0)
if row.CustomLabels.Valid {
_ = json.Unmarshal(row.CustomLabels.RawMessage, &customLabels)
}
res = append(res, entities.Scope{
ID: row.ID,
Name: row.Name,
Abbreviation: row.Abbreviation,
CustomLabels: customLabels,
})
}
return nil, err
}
func (r *scopeRepository) ListUser(ctx context.Context, userID string) ([]entities.Scope, error) {
rows, err := r.q.ListScopesByUser(ctx, userID)
if err != nil {
if err == sql.ErrNoRows {
return []entities.Scope{}, nil
}
return nil, err
}
res := make([]entities.Scope, 0, len(rows))
for _, row := range rows {
customLabels := make(map[string]string, 0)
if row.CustomLabels.Valid {
_ = json.Unmarshal(row.CustomLabels.RawMessage, &customLabels)
}
res = append(res, entities.Scope{
ID: row.ID,
Name: row.Name,
Abbreviation: row.Abbreviation,
CustomLabels: customLabels,
})
}
return res, nil
}
func (r *scopeRepository) Create(ctx context.Context, scope entities.Scope, ownerID string) (*entities.Scope, error) {
//TODO implement me
panic("implement me")
}
func (r *scopeRepository) Update(ctx context.Context, scope entities.Scope, update models.ScopeUpdate) error {
//TODO implement me
panic("implement me")
}
func (r *scopeRepository) Delete(ctx context.Context, scope entities.Scope) error {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()
q := r.q.WithTx(tx)
err = q.DeleteAllScopeMembers(ctx, scope.ID)
if err != nil {
return err
}
err = q.DeleteScope(ctx, scope.ID)
if err != nil {
return err
}
// TODO: Wipe everything
return tx.Commit()
}
func (r *scopeRepository) ListMembers(ctx context.Context, scopeIDs ...int) ([]entities.ScopeMember, error) {
res := make([]entities.ScopeMember, 0, 8)
if len(scopeIDs) == 1 {
rows, err := r.q.ListScopeMembers(ctx, scopeIDs[0])
if err != nil {
if err == sql.ErrNoRows {
return []entities.ScopeMember{}, nil
}
return nil, err
}
for _, row := range rows {
res = append(res, entities.ScopeMember{
ScopeID: row.ScopeID,
UserID: row.UserID,
Name: row.Name,
Owner: row.Owner,
})
}
} else {
for i := 0; i < len(scopeIDs); i += 6 {
ids := []int{-1, -2, -3, -4, -5, -6}
copy(ids, scopeIDs[i:])
rows, err := r.q.ListScopeMembersMulti(ctx, mysqlcore.ListScopeMembersMultiParams{
ScopeID: ids[0],
ScopeID_2: ids[1],
ScopeID_3: ids[2],
ScopeID_4: ids[3],
ScopeID_5: ids[4],
ScopeID_6: ids[5],
})
if err != nil {
if err == sql.ErrNoRows {
return []entities.ScopeMember{}, nil
}
return nil, err
}
for _, row := range rows {
res = append(res, entities.ScopeMember{
ScopeID: row.ScopeID,
UserID: row.UserID,
Name: row.Name,
Owner: row.Owner,
})
}
}
}
return res, nil
}
func (r *scopeRepository) SaveMember(ctx context.Context, member entities.ScopeMember) error {
return r.q.ReplaceScopeMember(ctx, mysqlcore.ReplaceScopeMemberParams{
ScopeID: member.ScopeID,
UserID: member.UserID,
Name: member.Name,
Owner: member.Owner,
})
}
func (r *scopeRepository) DeleteMember(ctx context.Context, member entities.ScopeMember) error {
return r.q.DeleteScopeMember(ctx, mysqlcore.DeleteScopeMemberParams{
ScopeID: member.ScopeID,
UserID: member.UserID,
})
}

55
ports/mysql/sqltypes/nulldate.go

@ -0,0 +1,55 @@
package sqltypes
import (
"database/sql/driver"
"errors"
"git.aiterp.net/stufflog3/stufflog3/models"
"time"
)
type NullDate struct {
Date models.Date
Valid bool
}
func (n *NullDate) AsPtr() *models.Date {
if n.Valid {
dateCopy := n.Date
return &dateCopy
} else {
return nil
}
}
func (n *NullDate) Scan(value interface{}) error {
if value == nil {
n.Valid = false
return nil
}
switch value := value.(type) {
case string:
date, err := models.ParseDate(value)
if err != nil {
return err
}
n.Date = date
n.Valid = true
case time.Time:
n.Date = models.Date{value.Year(), int(value.Month()), value.Day()}
n.Valid = true
default:
return errors.New("invalid type")
}
return nil
}
func (n NullDate) Value() (driver.Value, error) {
if !n.Valid {
return nil, nil
}
return n.Date.String(), nil
}

36
ports/mysql/sqltypes/nullrawmessage.go

@ -0,0 +1,36 @@
package sqltypes
import (
"database/sql/driver"
"encoding/json"
"errors"
)
type NullRawMessage struct {
RawMessage json.RawMessage
Valid bool
}
func (n *NullRawMessage) Scan(value interface{}) error {
if value == nil {
n.RawMessage, n.Valid = json.RawMessage{}, false
return nil
}
buf, ok := value.([]byte)
if !ok {
return errors.New("cannot parse to bytes")
}
n.RawMessage, n.Valid = buf, true
return nil
}
func (n NullRawMessage) Value() (driver.Value, error) {
if !n.Valid {
return nil, nil
}
return []byte(n.RawMessage), nil
}

91
ports/mysql/stats.go

@ -0,0 +1,91 @@
package mysql
import (
"context"
"database/sql"
"encoding/json"
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/models"
"git.aiterp.net/stufflog3/stufflog3/ports/mysql/mysqlcore"
)
type statsRepository struct {
db *sql.DB
q *mysqlcore.Queries
}
func (r *statsRepository) Find(ctx context.Context, scopeID, statID int) (*entities.Stat, error) {
row, err := r.q.GetStat(ctx, mysqlcore.GetStatParams{ScopeID: scopeID, ID: statID})
if err != nil {
if err == sql.ErrNoRows {
return nil, models.NotFoundError("Stat")
}
return nil, err
}
var allowedAmounts []models.StatAllowedAmount
if row.AllowedAmounts.Valid {
allowedAmounts = make([]models.StatAllowedAmount, 0, 8)
_ = json.Unmarshal(row.AllowedAmounts.RawMessage, &allowedAmounts)
if len(allowedAmounts) == 0 {
allowedAmounts = nil
}
}
return &entities.Stat{
ID: row.ID,
Name: row.Name,
Weight: row.Weight,
Description: row.Description,
AllowedAmounts: allowedAmounts,
}, nil
}
func (r *statsRepository) List(ctx context.Context, scopeID int) ([]entities.Stat, error) {
rows, err := r.q.ListStats(ctx, scopeID)
if err != nil {
if err == sql.ErrNoRows {
return nil, models.NotFoundError("Stat")
}
return nil, err
}
res := make([]entities.Stat, 0, len(rows))
for _, row := range rows {
var allowedAmounts []models.StatAllowedAmount
if row.AllowedAmounts.Valid {
allowedAmounts = make([]models.StatAllowedAmount, 0, 8)
_ = json.Unmarshal(row.AllowedAmounts.RawMessage, &allowedAmounts)
if len(allowedAmounts) == 0 {
allowedAmounts = nil
}
}
res = append(res, entities.Stat{
ID: row.ID,
Name: row.Name,
Weight: row.Weight,
Description: row.Description,
AllowedAmounts: allowedAmounts,
})
}
return res, nil
}
func (r *statsRepository) Insert(ctx context.Context, stat entities.Stat) (*entities.Stat, error) {
//TODO implement me
panic("implement me")
}
func (r *statsRepository) Update(ctx context.Context, stat entities.Stat, update models.StatUpdate) error {
//TODO implement me
panic("implement me")
}
func (r *statsRepository) Delete(ctx context.Context, stat entities.Stat) error {
//TODO implement me
panic("implement me")
}

14
scripts/goose-mysql/20220313115117_scope.sql

@ -0,0 +1,14 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE scope (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`abbreviation` CHAR(8) NOT NULL,
`custom_labels` JSON
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS scope;
-- +goose StatementEnd

17
scripts/goose-mysql/20220313115122_scope_member.sql

@ -0,0 +1,17 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE scope_member (
`scope_id` INT NOT NULL,
`user_id` CHAR(36) NOT NULL,
`name` VARCHAR(63) NOT NULL,
`owner` BOOLEAN NOT NULL,
PRIMARY KEY (`scope_id`, `user_id`),
UNIQUE (`scope_id`, `name`)
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS scope_member;
-- +goose StatementEnd

18
scripts/goose-mysql/20220326173144_stat.sql

@ -0,0 +1,18 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE stat (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`scope_id` INT NOT NULL,
`name` VARCHAR(255) NOT NULL,
`description` TEXT NOT NULL,
`weight` FLOAT NOT NULL,
`allowed_amounts` JSON,
UNIQUE (`scope_id`, `name`)
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS stat;
-- +goose StatementEnd

18
scripts/goose-mysql/20220326174046_project.sql

@ -0,0 +1,18 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE project (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`scope_id` INT NOT NULL,
`owner_id` CHAR(36) NOT NULL,
`name` VARCHAR(255) NOT NULL,
`status` INT NOT NULL,
`description` TEXT NOT NULL,
UNIQUE (`scope_id`, `name`)
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS project;
-- +goose StatementEnd

16
scripts/goose-mysql/20220404144911_project_requirement.sql

@ -0,0 +1,16 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE project_requirement (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`scope_id` INT NOT NULL,
`project_id` INT NOT NULL,
`name` VARCHAR(255) NOT NULL,
`status` INT NOT NULL,
`description` TEXT NOT NULL
)
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS project_requirement;
-- +goose StatementEnd

19
scripts/goose-mysql/20220404144914_item.sql

@ -0,0 +1,19 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE item (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`scope_id` INT NOT NULL,
`project_requirement_id` INT,
`owner_id` CHAR(36) NOT NULL,
`name` VARCHAR(255) NOT NULL,
`description` TEXT NOT NULL,
`created_time` DATETIME NOT NULL,
`acquired_time` DATETIME,
`scheduled_date` DATE
)
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS item;
-- +goose StatementEnd

17
scripts/goose-mysql/20220404144947_item_stat_progress.sql

@ -0,0 +1,17 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE item_stat_progress (
`item_id` INT NOT NULL,
`stat_id` INT NOT NULL,
`acquired` INT NOT NULL,
`required` INT NOT NULL,
PRIMARY KEY (`item_id`, `stat_id`),
UNIQUE (`stat_id`, `item_id`)
)
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS item_stat_progress;
-- +goose StatementEnd

15
scripts/goose-mysql/20220404184237_project_requirement_stat.sql

@ -0,0 +1,15 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE project_requirement_stat (
`project_requirement_id` INT NOT NULL,
`stat_id` INT NOT NULL,
`required` INT NOT NULL,
PRIMARY KEY (`project_requirement_id`, `stat_id`)
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS project_requirement_stat;
-- +goose StatementEnd

9
scripts/goose-mysql/20220411190154_project_created_time.sql

@ -0,0 +1,9 @@
-- +goose Up
-- +goose StatementBegin
ALTER TABLE project ADD COLUMN created_time TIMESTAMP NOT NULL DEFAULT NOW();
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
ALTER TABLE project DROP COLUMN IF EXISTS created_time;
-- +goose StatementEnd

22
scripts/goose-mysql/20220502181149_sprint.sql

@ -0,0 +1,22 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE sprint (
`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
`scope_id` INT NOT NULL,
`name` VARCHAR(255) NOT NULL,
`description` TEXT NOT NULL,
`from_time` DATETIME NOT NULL,
`to_time` DATETIME NOT NULL,
`is_timed` BOOLEAN NOT NULL,
`is_coarse` BOOLEAN NOT NULL,
`is_unweighted` BOOLEAN NOT NULL,
`kind` INT NOT NULL,
`aggregate_name` VARCHAR(255) NOT NULL,
`aggregate_required` INT NOT NULL
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS sprint;
-- +goose StatementEnd

16
scripts/goose-mysql/20220502181235_sprint_part.sql

@ -0,0 +1,16 @@
-- +goose Up
-- +goose StatementBegin
CREATE TABLE sprint_part (
`sprint_id` INT NOT NULL,
`object_id` INT NOT NULL,
`required` INT NOT NULL,
PRIMARY KEY (sprint_id, object_id),
UNIQUE (object_id, sprint_id)
);
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
DROP TABLE IF EXISTS sprint_part;
-- +goose StatementEnd

30
sqlc.yaml

@ -0,0 +1,30 @@
version: "1"
packages:
- name: "mysqlcore"
path: "./ports/mysql/mysqlcore"
queries: "./ports/mysql/queries"
schema: "./scripts/goose-mysql"
engine: "mysql"
emit_prepared_queries: true
emit_interface: false
emit_exact_table_names: false
emit_empty_slices: true
emit_json_tags: false
overrides:
- go_type: "git.aiterp.net/stufflog3/stufflog3/ports/mysql/sqltypes.NullRawMessage"
db_type: "json"
nullable: true
- go_type: "git.aiterp.net/stufflog3/stufflog3/ports/mysql/sqltypes.NullDate"
db_type: "date"
nullable: true
- go_type: "float64"
db_type: "float"
- go_type: "float64"
db_type: "float"
nullable: true
- go_type: "int"
db_type: "int"
- column: "isp.total_acquired"
go_type: "int"
- column: "isp.total_required"
go_type: "int"

22
usecases/auth/service.go

@ -0,0 +1,22 @@
package auth
import (
"context"
)
type Service struct {
key struct{ Stuff uint64 }
}
func (s *Service) ContextWithUser(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, &s.key, id)
}
func (s *Service) GetUser(ctx context.Context) string {
v := ctx.Value(&s.key)
if v == nil {
return ""
}
return v.(string)
}

22
usecases/items/repository.go

@ -0,0 +1,22 @@
package items
import (
"context"
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/models"
"time"
)
type Repository interface {
Find(ctx context.Context, scopeID, itemID int) (*entities.Item, error)
ListCreated(ctx context.Context, scopeID int, from, to time.Time) ([]entities.Item, error)
ListAcquired(ctx context.Context, scopeID int, from, to time.Time) ([]entities.Item, error)
ListScheduled(ctx context.Context, scopeID int, from, to models.Date) ([]entities.Item, error)
ListRequirement(ctx context.Context, requirementID int) ([]entities.Item, error)
ListProject(ctx context.Context, projectID int) ([]entities.Item, error)
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
}

28
usecases/items/service.go

@ -0,0 +1,28 @@
package items
import (
"context"
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
"git.aiterp.net/stufflog3/stufflog3/usecases/stats"
)
type Service struct {
Scopes *scopes.Service
Stats *stats.Service
Repository Repository
}
func (s *Service) ListProject(ctx context.Context, requirementID int) ([]entities.Item, []entities.ItemProgress, error) {
items, err := s.Repository.ListProject(ctx, requirementID)
if err != nil {
return nil, nil, err
}
itemProgresses, err := s.Repository.ListProgress(ctx, items...)
if err != nil {
return nil, nil, err
}
return items, itemProgresses, nil
}

21
usecases/projects/repository.go

@ -0,0 +1,21 @@
package projects
import (
"context"
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/models"
)
type Repository interface {
Find(ctx context.Context, scopeID, projectID int) (*entities.Project, error)
List(ctx context.Context, scopeID int) ([]entities.Project, error)
Create(ctx context.Context, project entities.Project) (*entities.Project, error)
Update(ctx context.Context, project entities.Project, update models.ProjectUpdate) error
Delete(ctx context.Context, project entities.Project) error
ListRequirements(ctx context.Context, projectID int) ([]entities.Requirement, []entities.RequirementStat, error)
CreateRequirement(ctx context.Context, requirement entities.Requirement) (*entities.Requirement, error)
UpdateRequirement(ctx context.Context, requirement entities.Requirement, update models.RequirementUpdate) error
DeleteRequirement(ctx context.Context, requirement entities.Requirement) error
UpsertRequirementStat(ctx context.Context, stat entities.RequirementStat) error
DeleteRequirementStat(ctx context.Context, stat entities.RequirementStat) error
}

143
usecases/projects/result.go

@ -0,0 +1,143 @@
package projects
import (
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/models"
"time"
)
type Entry struct {
ID int `json:"id"`
OwnerID string `json:"ownerId"`
CreatedTime time.Time `json:"createdTime"`
Name string `json:"name"`
Status models.Status `json:"status"`
}
func generateEntry(project entities.Project) Entry {
return Entry{
ID: project.ID,
OwnerID: project.OwnerID,
CreatedTime: project.CreatedTime,
Name: project.Name,
Status: project.Status,
}
}
type Result struct {
entities.Project
Requirements []ResultRequirement `json:"requirements"`
}
func GenerateResult(
project entities.Project,
requirement []entities.Requirement,
requirementStats []entities.RequirementStat,
items []entities.Item,
itemProgresses []entities.ItemProgress,
stats []entities.Stat,
) Result {
res := Result{
Project: project,
Requirements: make([]ResultRequirement, 0, 8),
}
for _, req := range requirement {
if req.ProjectID != project.ID {
continue
}
resReq := ResultRequirement{
ID: req.ID,
Name: req.Name,
Description: req.Description,
Status: req.Status,
Stats: make([]ResultRequirementStat, 0, 8),
Items: make([]ResultRequirementItem, 0, 8),
}
statIndices := make(map[int]int)
for _, reqStat := range requirementStats {
if reqStat.RequirementID != req.ID {
continue
}
resStat := ResultRequirementStat{
ID: reqStat.StatID,
Required: reqStat.Required,
}
for _, stat := range stats {
if stat.ID == resStat.ID {
resStat.Name = stat.Name
resStat.Weight = stat.Weight
break
}
}
resReq.Stats = append(resReq.Stats, resStat)
statIndices[reqStat.StatID] = len(resReq.Stats) - 1
}
for _, item := range items {
if item.ProjectRequirementID == nil || *item.ProjectRequirementID != req.ID {
continue
}
resItem := ResultRequirementItem{
Item: item,
Stats: make([]ResultRequirementStat, 0, 8),
}
for _, itemProgress := range itemProgresses {
if itemProgress.ItemID != item.ID {
continue
}
resStat := ResultRequirementStat{
ID: itemProgress.StatID,
Acquired: itemProgress.Acquired,
Required: itemProgress.Required,
}
for _, stat := range stats {
if stat.ID == resStat.ID {
resStat.Name = stat.Name
resStat.Weight = stat.Weight
break
}
}
if si, ok := statIndices[resStat.ID]; ok {
resReq.Stats[si].Acquired += itemProgress.Acquired
}
resItem.Stats = append(resItem.Stats, resStat)
}
resReq.Items = append(resReq.Items, resItem)
}
res.Requirements = append(res.Requirements, resReq)
}
return res
}
type ResultRequirement struct {
ID int `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Status models.Status `json:"status"`
Stats []ResultRequirementStat `json:"stats"`
Items []ResultRequirementItem `json:"items"`
}
type ResultRequirementStat struct {
ID int `json:"id"`
Name string `json:"name"`
Weight float64 `json:"weight"`
Acquired int `json:"acquired"`
Required int `json:"required"`
}
type ResultRequirementItem struct {
entities.Item
Stats []ResultRequirementStat `json:"stats"`
}

65
usecases/projects/service.go

@ -0,0 +1,65 @@
package projects
import (
"context"
"git.aiterp.net/stufflog3/stufflog3/usecases/items"
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
"git.aiterp.net/stufflog3/stufflog3/usecases/stats"
)
type Service struct {
Scopes *scopes.Service
Stats *stats.Service
Items *items.Service
Repository Repository
}
func (s *Service) Find(ctx context.Context, id int) (*Result, error) {
scopeID := s.Scopes.ScopeFromContext(ctx).ID
project, err := s.Repository.Find(ctx, scopeID, id)
if err != nil {
return nil, err
}
requirements, requirementStats, err := s.Repository.ListRequirements(ctx, id)
if err != nil {
return nil, err
}
items, itemProgresses, err := s.Items.ListProject(ctx, id)
if err != nil {
return nil, err
}
stats, err := s.Stats.List(ctx)
if err != nil {
return nil, err
}
result := GenerateResult(
*project,
requirements,
requirementStats,
items,
itemProgresses,
stats,
)
return &result, nil
}
func (s *Service) List(ctx context.Context) ([]Entry, error) {
projects, err := s.Repository.List(ctx, s.Scopes.ScopeFromContext(ctx).ID)
if err != nil {
return nil, err
}
entries := make([]Entry, 0, len(projects))
for _, project := range projects {
entries = append(entries, generateEntry(project))
}
return entries, nil
}

20
usecases/scopes/repository.go

@ -0,0 +1,20 @@
package scopes
import (
"context"
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/models"
)
type Repository interface {
Find(ctx context.Context, id int) (*entities.Scope, error)
List(ctx context.Context) ([]entities.Scope, error)
ListUser(ctx context.Context, userID string) ([]entities.Scope, error)
Create(ctx context.Context, scope entities.Scope, ownerID string) (*entities.Scope, error)
Update(ctx context.Context, scope entities.Scope, update models.ScopeUpdate) error
Delete(ctx context.Context, scope entities.Scope) error
ListMembers(ctx context.Context, scopeIDs ...int) ([]entities.ScopeMember, error)
SaveMember(ctx context.Context, member entities.ScopeMember) error
DeleteMember(ctx context.Context, member entities.ScopeMember) error
}

73
usecases/scopes/service.go

@ -0,0 +1,73 @@
package scopes
import (
"context"
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/models"
"git.aiterp.net/stufflog3/stufflog3/usecases/auth"
)
type Service struct {
Auth *auth.Service
Repository Repository
contextKey struct{}
}
func (s *Service) ContextWithScope(ctx context.Context, scope entities.Scope) context.Context {
return context.WithValue(ctx, &s.contextKey, &scope)
}
func (s *Service) ScopeFromContext(ctx context.Context) *entities.Scope {
v := ctx.Value(&s.contextKey)
if v == nil {
return nil
}
return v.(*entities.Scope)
}
// Find finds a scope and its members, and returns it if the logged-in user is part of this list.
func (s *Service) Find(ctx context.Context, id int) (*entities.Scope, []entities.ScopeMember, error) {
scope, err := s.Repository.Find(ctx, id)
if err != nil {
return nil, nil, err
}
members, err := s.Repository.ListMembers(ctx, scope.ID)
if err != nil {
return nil, nil, err
}
userID := s.Auth.GetUser(ctx)
found := false
for _, member := range members {
if member.UserID == userID {
found = true
break
}
}
if !found {
return nil, nil, models.NotFoundError("Scope")
}
return scope, members, nil
}
// List lists a scope and their members, and returns it if the logged-in user is part of this list.
func (s *Service) List(ctx context.Context) ([]entities.Scope, []entities.ScopeMember, error) {
scopes, err := s.Repository.ListUser(ctx, s.Auth.GetUser(ctx))
if err != nil {
return nil, nil, err
}
ids := make([]int, 0, len(scopes))
for _, scope := range scopes {
ids = append(ids, scope.ID)
}
members, err := s.Repository.ListMembers(ctx, ids...)
if err != nil {
return nil, nil, err
}
return scopes, members, nil
}

15
usecases/stats/repository.go

@ -0,0 +1,15 @@
package stats
import (
"context"
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/models"
)
type Repository interface {
Find(ctx context.Context, scopeID, statID int) (*entities.Stat, error)
List(ctx context.Context, scopeID int) ([]entities.Stat, error)
Insert(ctx context.Context, stat entities.Stat) (*entities.Stat, error)
Update(ctx context.Context, stat entities.Stat, update models.StatUpdate) error
Delete(ctx context.Context, stat entities.Stat) error
}

17
usecases/stats/service.go

@ -0,0 +1,17 @@
package stats
import (
"context"
"git.aiterp.net/stufflog3/stufflog3/entities"
"git.aiterp.net/stufflog3/stufflog3/usecases/scopes"
)
type Service struct {
Scopes *scopes.Service
Repository Repository
}
func (s *Service) List(ctx context.Context) ([]entities.Stat, error) {
return s.Repository.List(ctx, s.Scopes.ScopeFromContext(ctx).ID)
}
Loading…
Cancel
Save