From e2d098e03f3da4c05b8c92b42376eaa0efaa02af Mon Sep 17 00:00:00 2001 From: Gisle Aune Date: Sat, 14 May 2022 13:42:59 +0200 Subject: [PATCH] first commit --- .gitignore | 2 + cmd/stufflog3-local/main.go | 104 +++ entities/item.go | 57 ++ entities/project.go | 58 ++ entities/scope.go | 15 + entities/sprint.go | 59 ++ entities/stat.go | 42 + go.mod | 8 + go.sum | 51 ++ models/date.go | 46 + models/errors.go | 47 + models/item.go | 15 + models/project.go | 14 + models/scope.go | 6 + models/sprint.go | 23 + models/stat.go | 13 + models/status.go | 29 + ports/httpapi/auth.go | 71 ++ ports/httpapi/common.go | 68 ++ ports/httpapi/projects.go | 21 + ports/httpapi/scopes.go | 104 +++ ports/mysql/db.go | 90 ++ ports/mysql/items.go | 125 +++ ports/mysql/mysqlcore/db.go | 598 +++++++++++++ ports/mysql/mysqlcore/items.sql.go | 805 ++++++++++++++++++ ports/mysql/mysqlcore/models.go | 100 +++ ports/mysql/mysqlcore/projects.sql.go | 323 +++++++ ports/mysql/mysqlcore/scopes.sql.go | 278 ++++++ ports/mysql/mysqlcore/stats.sql.go | 155 ++++ ports/mysql/projects.go | 246 ++++++ ports/mysql/queries/items.sql | 114 +++ ports/mysql/queries/projects.sql | 60 ++ ports/mysql/queries/scopes.sql | 43 + ports/mysql/queries/stats.sql | 26 + ports/mysql/scopes.go | 197 +++++ ports/mysql/sqltypes/nulldate.go | 55 ++ ports/mysql/sqltypes/nullrawmessage.go | 36 + ports/mysql/stats.go | 91 ++ scripts/goose-mysql/20220313115117_scope.sql | 14 + .../20220313115122_scope_member.sql | 17 + scripts/goose-mysql/20220326173144_stat.sql | 18 + .../goose-mysql/20220326174046_project.sql | 18 + .../20220404144911_project_requirement.sql | 16 + scripts/goose-mysql/20220404144914_item.sql | 19 + .../20220404144947_item_stat_progress.sql | 17 + ...0220404184237_project_requirement_stat.sql | 15 + .../20220411190154_project_created_time.sql | 9 + scripts/goose-mysql/20220502181149_sprint.sql | 22 + .../20220502181235_sprint_part.sql | 16 + sqlc.yaml | 30 + usecases/auth/service.go | 22 + usecases/items/repository.go | 22 + usecases/items/service.go | 28 + usecases/projects/repository.go | 21 + usecases/projects/result.go | 143 ++++ usecases/projects/service.go | 65 ++ usecases/scopes/repository.go | 20 + usecases/scopes/service.go | 73 ++ usecases/stats/repository.go | 15 + usecases/stats/service.go | 17 + 60 files changed, 4832 insertions(+) create mode 100644 .gitignore create mode 100644 cmd/stufflog3-local/main.go create mode 100644 entities/item.go create mode 100644 entities/project.go create mode 100644 entities/scope.go create mode 100644 entities/sprint.go create mode 100644 entities/stat.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 models/date.go create mode 100644 models/errors.go create mode 100644 models/item.go create mode 100644 models/project.go create mode 100644 models/scope.go create mode 100644 models/sprint.go create mode 100644 models/stat.go create mode 100644 models/status.go create mode 100644 ports/httpapi/auth.go create mode 100644 ports/httpapi/common.go create mode 100644 ports/httpapi/projects.go create mode 100644 ports/httpapi/scopes.go create mode 100644 ports/mysql/db.go create mode 100644 ports/mysql/items.go create mode 100644 ports/mysql/mysqlcore/db.go create mode 100644 ports/mysql/mysqlcore/items.sql.go create mode 100644 ports/mysql/mysqlcore/models.go create mode 100644 ports/mysql/mysqlcore/projects.sql.go create mode 100644 ports/mysql/mysqlcore/scopes.sql.go create mode 100644 ports/mysql/mysqlcore/stats.sql.go create mode 100644 ports/mysql/projects.go create mode 100644 ports/mysql/queries/items.sql create mode 100644 ports/mysql/queries/projects.sql create mode 100644 ports/mysql/queries/scopes.sql create mode 100644 ports/mysql/queries/stats.sql create mode 100644 ports/mysql/scopes.go create mode 100644 ports/mysql/sqltypes/nulldate.go create mode 100644 ports/mysql/sqltypes/nullrawmessage.go create mode 100644 ports/mysql/stats.go create mode 100644 scripts/goose-mysql/20220313115117_scope.sql create mode 100644 scripts/goose-mysql/20220313115122_scope_member.sql create mode 100644 scripts/goose-mysql/20220326173144_stat.sql create mode 100644 scripts/goose-mysql/20220326174046_project.sql create mode 100644 scripts/goose-mysql/20220404144911_project_requirement.sql create mode 100644 scripts/goose-mysql/20220404144914_item.sql create mode 100644 scripts/goose-mysql/20220404144947_item_stat_progress.sql create mode 100644 scripts/goose-mysql/20220404184237_project_requirement_stat.sql create mode 100644 scripts/goose-mysql/20220411190154_project_created_time.sql create mode 100644 scripts/goose-mysql/20220502181149_sprint.sql create mode 100644 scripts/goose-mysql/20220502181235_sprint_part.sql create mode 100644 sqlc.yaml create mode 100644 usecases/auth/service.go create mode 100644 usecases/items/repository.go create mode 100644 usecases/items/service.go create mode 100644 usecases/projects/repository.go create mode 100644 usecases/projects/result.go create mode 100644 usecases/projects/service.go create mode 100644 usecases/scopes/repository.go create mode 100644 usecases/scopes/service.go create mode 100644 usecases/stats/repository.go create mode 100644 usecases/stats/service.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..60ac8bc --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.idea +.env diff --git a/cmd/stufflog3-local/main.go b/cmd/stufflog3-local/main.go new file mode 100644 index 0000000..81641e4 --- /dev/null +++ b/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 +} diff --git a/entities/item.go b/entities/item.go new file mode 100644 index 0000000..46664cf --- /dev/null +++ b/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 + } +} diff --git a/entities/project.go b/entities/project.go new file mode 100644 index 0000000..098f91f --- /dev/null +++ b/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"` +} diff --git a/entities/scope.go b/entities/scope.go new file mode 100644 index 0000000..a94f5bd --- /dev/null +++ b/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"` +} diff --git a/entities/sprint.go b/entities/sprint.go new file mode 100644 index 0000000..c568e9f --- /dev/null +++ b/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 + } +} diff --git a/entities/stat.go b/entities/stat.go new file mode 100644 index 0000000..ec1d783 --- /dev/null +++ b/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) + } + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..37902d1 --- /dev/null +++ b/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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..679bde7 --- /dev/null +++ b/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= diff --git a/models/date.go b/models/date.go new file mode 100644 index 0000000..ddfdde0 --- /dev/null +++ b/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) +} diff --git a/models/errors.go b/models/errors.go new file mode 100644 index 0000000..013c8ca --- /dev/null +++ b/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 +} diff --git a/models/item.go b/models/item.go new file mode 100644 index 0000000..eea42aa --- /dev/null +++ b/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"` +} diff --git a/models/project.go b/models/project.go new file mode 100644 index 0000000..80c23aa --- /dev/null +++ b/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"` +} diff --git a/models/scope.go b/models/scope.go new file mode 100644 index 0000000..202ab81 --- /dev/null +++ b/models/scope.go @@ -0,0 +1,6 @@ +package models + +type ScopeUpdate struct { + Name *string + Abbreviation *string +} diff --git a/models/sprint.go b/models/sprint.go new file mode 100644 index 0000000..2c37c10 --- /dev/null +++ b/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 +) diff --git a/models/stat.go b/models/stat.go new file mode 100644 index 0000000..4fd90af --- /dev/null +++ b/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"` +} diff --git a/models/status.go b/models/status.go new file mode 100644 index 0000000..3f22dc5 --- /dev/null +++ b/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", +} diff --git a/ports/httpapi/auth.go b/ports/httpapi/auth.go new file mode 100644 index 0000000..8c97508 --- /dev/null +++ b/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) + } + } +} diff --git a/ports/httpapi/common.go b/ports/httpapi/common.go new file mode 100644 index 0000000..4d91b36 --- /dev/null +++ b/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 +} diff --git a/ports/httpapi/projects.go b/ports/httpapi/projects.go new file mode 100644 index 0000000..f7aa5d8 --- /dev/null +++ b/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) + })) +} diff --git a/ports/httpapi/scopes.go b/ports/httpapi/scopes.go new file mode 100644 index 0000000..2d3fa03 --- /dev/null +++ b/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 + })) +} diff --git a/ports/mysql/db.go b/ports/mysql/db.go new file mode 100644 index 0000000..7d41fff --- /dev/null +++ b/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 + } +} diff --git a/ports/mysql/items.go b/ports/mysql/items.go new file mode 100644 index 0000000..8cad724 --- /dev/null +++ b/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") +} diff --git a/ports/mysql/mysqlcore/db.go b/ports/mysql/mysqlcore/db.go new file mode 100644 index 0000000..aeb9b51 --- /dev/null +++ b/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, + } +} diff --git a/ports/mysql/mysqlcore/items.sql.go b/ports/mysql/mysqlcore/items.sql.go new file mode 100644 index 0000000..71842d7 --- /dev/null +++ b/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 +} diff --git a/ports/mysql/mysqlcore/models.go b/ports/mysql/mysqlcore/models.go new file mode 100644 index 0000000..dddc0cf --- /dev/null +++ b/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 +} diff --git a/ports/mysql/mysqlcore/projects.sql.go b/ports/mysql/mysqlcore/projects.sql.go new file mode 100644 index 0000000..23d639a --- /dev/null +++ b/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 +} diff --git a/ports/mysql/mysqlcore/scopes.sql.go b/ports/mysql/mysqlcore/scopes.sql.go new file mode 100644 index 0000000..f76011c --- /dev/null +++ b/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 +} diff --git a/ports/mysql/mysqlcore/stats.sql.go b/ports/mysql/mysqlcore/stats.sql.go new file mode 100644 index 0000000..27f2b8d --- /dev/null +++ b/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 +} diff --git a/ports/mysql/projects.go b/ports/mysql/projects.go new file mode 100644 index 0000000..d6b9c54 --- /dev/null +++ b/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, + }) +} diff --git a/ports/mysql/queries/items.sql b/ports/mysql/queries/items.sql new file mode 100644 index 0000000..024f7b6 --- /dev/null +++ b/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 = ?; + diff --git a/ports/mysql/queries/projects.sql b/ports/mysql/queries/projects.sql new file mode 100644 index 0000000..ef2a7a5 --- /dev/null +++ b/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 = ?; diff --git a/ports/mysql/queries/scopes.sql b/ports/mysql/queries/scopes.sql new file mode 100644 index 0000000..8eaad37 --- /dev/null +++ b/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=?; diff --git a/ports/mysql/queries/stats.sql b/ports/mysql/queries/stats.sql new file mode 100644 index 0000000..4306f44 --- /dev/null +++ b/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 = ?; diff --git a/ports/mysql/scopes.go b/ports/mysql/scopes.go new file mode 100644 index 0000000..8009cbd --- /dev/null +++ b/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, + }) +} diff --git a/ports/mysql/sqltypes/nulldate.go b/ports/mysql/sqltypes/nulldate.go new file mode 100644 index 0000000..eb4493b --- /dev/null +++ b/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 +} diff --git a/ports/mysql/sqltypes/nullrawmessage.go b/ports/mysql/sqltypes/nullrawmessage.go new file mode 100644 index 0000000..6fb957f --- /dev/null +++ b/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 +} diff --git a/ports/mysql/stats.go b/ports/mysql/stats.go new file mode 100644 index 0000000..673e011 --- /dev/null +++ b/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") +} diff --git a/scripts/goose-mysql/20220313115117_scope.sql b/scripts/goose-mysql/20220313115117_scope.sql new file mode 100644 index 0000000..22c0d3d --- /dev/null +++ b/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 diff --git a/scripts/goose-mysql/20220313115122_scope_member.sql b/scripts/goose-mysql/20220313115122_scope_member.sql new file mode 100644 index 0000000..a810f04 --- /dev/null +++ b/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 diff --git a/scripts/goose-mysql/20220326173144_stat.sql b/scripts/goose-mysql/20220326173144_stat.sql new file mode 100644 index 0000000..5daba36 --- /dev/null +++ b/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 diff --git a/scripts/goose-mysql/20220326174046_project.sql b/scripts/goose-mysql/20220326174046_project.sql new file mode 100644 index 0000000..5b17486 --- /dev/null +++ b/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 diff --git a/scripts/goose-mysql/20220404144911_project_requirement.sql b/scripts/goose-mysql/20220404144911_project_requirement.sql new file mode 100644 index 0000000..6caecfd --- /dev/null +++ b/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 diff --git a/scripts/goose-mysql/20220404144914_item.sql b/scripts/goose-mysql/20220404144914_item.sql new file mode 100644 index 0000000..e0f4ee4 --- /dev/null +++ b/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 diff --git a/scripts/goose-mysql/20220404144947_item_stat_progress.sql b/scripts/goose-mysql/20220404144947_item_stat_progress.sql new file mode 100644 index 0000000..da5a666 --- /dev/null +++ b/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 diff --git a/scripts/goose-mysql/20220404184237_project_requirement_stat.sql b/scripts/goose-mysql/20220404184237_project_requirement_stat.sql new file mode 100644 index 0000000..1fec0d4 --- /dev/null +++ b/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 diff --git a/scripts/goose-mysql/20220411190154_project_created_time.sql b/scripts/goose-mysql/20220411190154_project_created_time.sql new file mode 100644 index 0000000..1f44a4f --- /dev/null +++ b/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 diff --git a/scripts/goose-mysql/20220502181149_sprint.sql b/scripts/goose-mysql/20220502181149_sprint.sql new file mode 100644 index 0000000..d7c9d34 --- /dev/null +++ b/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 diff --git a/scripts/goose-mysql/20220502181235_sprint_part.sql b/scripts/goose-mysql/20220502181235_sprint_part.sql new file mode 100644 index 0000000..82c3f72 --- /dev/null +++ b/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 diff --git a/sqlc.yaml b/sqlc.yaml new file mode 100644 index 0000000..4e1730e --- /dev/null +++ b/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" \ No newline at end of file diff --git a/usecases/auth/service.go b/usecases/auth/service.go new file mode 100644 index 0000000..28f9460 --- /dev/null +++ b/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) +} diff --git a/usecases/items/repository.go b/usecases/items/repository.go new file mode 100644 index 0000000..ff3423c --- /dev/null +++ b/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 +} diff --git a/usecases/items/service.go b/usecases/items/service.go new file mode 100644 index 0000000..2e076bf --- /dev/null +++ b/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 +} diff --git a/usecases/projects/repository.go b/usecases/projects/repository.go new file mode 100644 index 0000000..252aea1 --- /dev/null +++ b/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 +} diff --git a/usecases/projects/result.go b/usecases/projects/result.go new file mode 100644 index 0000000..e5f6bd4 --- /dev/null +++ b/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"` +} diff --git a/usecases/projects/service.go b/usecases/projects/service.go new file mode 100644 index 0000000..71afb7c --- /dev/null +++ b/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 +} diff --git a/usecases/scopes/repository.go b/usecases/scopes/repository.go new file mode 100644 index 0000000..7b86e43 --- /dev/null +++ b/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 +} diff --git a/usecases/scopes/service.go b/usecases/scopes/service.go new file mode 100644 index 0000000..588ac44 --- /dev/null +++ b/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 +} diff --git a/usecases/stats/repository.go b/usecases/stats/repository.go new file mode 100644 index 0000000..245e6c3 --- /dev/null +++ b/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 +} diff --git a/usecases/stats/service.go b/usecases/stats/service.go new file mode 100644 index 0000000..8f31c6c --- /dev/null +++ b/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) +}