From a249110622b32938936e88985f613a3761e3d6b9 Mon Sep 17 00:00:00 2001 From: Gisle Aune Date: Sat, 23 Apr 2022 14:07:10 +0200 Subject: [PATCH] a lot of progress on items as well as get/find/create on project. --- api/common.go | 31 +- api/items.go | 129 ++++- api/projects.go | 50 ++ api/scope.go | 2 + cmd/stufflog3-server.go | 1 + go.mod | 22 +- internal/database/database.go | 7 +- internal/database/mysql/database.go | 23 +- internal/database/mysql/items.go | 38 +- internal/database/mysql/mysqlcore/db.go | 491 +++++++++++++++++- internal/database/mysql/mysqlcore/item.sql.go | 67 ++- .../database/mysql/mysqlcore/project.sql.go | 201 ++++++- .../database/mysql/mysqlcore/scope.sql.go | 24 +- .../database/mysql/mysqlcore/stats.sql.go | 2 +- internal/database/mysql/project.go | 180 +++++++ internal/database/mysql/queries/item.sql | 27 +- internal/database/mysql/queries/project.sql | 39 +- internal/database/mysql/scopes.go | 33 +- internal/models/item.go | 2 - internal/models/project.go | 47 +- internal/models/scope.go | 9 +- internal/models/status.go | 20 +- internal/slerrors/notfound.go | 2 +- sqlc.yaml | 2 +- 24 files changed, 1302 insertions(+), 147 deletions(-) create mode 100644 api/projects.go create mode 100644 internal/database/mysql/project.go diff --git a/api/common.go b/api/common.go index 5a44c4c..5c62acd 100644 --- a/api/common.go +++ b/api/common.go @@ -20,12 +20,36 @@ func handler(key string, callback func(c *gin.Context) (interface{}, error)) gin } resJson := make(map[string]interface{}, 1) + + if s := getScope(c); s != nil { + options := ScopeOptions{ + StatusLabels: models.StatusLabels, + } + + if s.Stats != nil { + options.Stats = make(map[int]models.Stat) + for _, stat := range s.Stats { + options.Stats[stat.ID] = stat + } + } + options.Members = make(map[string]string) + for _, member := range s.Members { + options.Members[member.ID] = member.Name + } + resJson["scopeInfo"] = options + } resJson[key] = res c.JSON(200, resJson) } } +type ScopeOptions struct { + StatusLabels map[models.Status]string `json:"statusLabels"` + Stats map[int]models.Stat `json:"stats,omitempty"` + Members map[string]string `json:"members,omitempty"` +} + func reqInt(c *gin.Context, key string) (int, error) { v, err := strconv.Atoi(c.Param(key)) if err != nil { @@ -66,5 +90,10 @@ func scopeIDMiddleware(db database.Database) gin.HandlerFunc { } func getScope(c *gin.Context) *models.Scope { - return c.Request.Context().Value(&scopeIdContextKey).(*models.Scope) + scope := c.Request.Context().Value(&scopeIdContextKey) + if scope == nil { + return nil + } + + return scope.(*models.Scope) } diff --git a/api/items.go b/api/items.go index 524e65a..92600bc 100644 --- a/api/items.go +++ b/api/items.go @@ -12,23 +12,71 @@ import ( func Items(g *gin.RouterGroup, db database.Database) { g.Use(scopeIDMiddleware(db)) - g.GET("/:id", handler("item", func(c *gin.Context) (interface{}, error) { - id, err := reqInt(c, "id") - if err != nil { - return nil, err + g.GET("/", handler("items", func(c *gin.Context) (res interface{}, err error) { + mode := c.Query("mode") + + var fromTime, toTime time.Time + var fromDate, toDate models.Date + if mode != "scheduled" { + if c.Query("to") == "" { + toTime = time.Now() + } else { + toTime, err = time.Parse(time.RFC3339Nano, c.Query("to")) + if err != nil { + return nil, slerrors.BadRequest("Invalid value for to") + } + } + if c.Query("from") == "" { + fromTime = time.Now().Add(-time.Hour * 24 * 30) + } else { + fromTime, err = time.Parse(time.RFC3339Nano, c.Query("from")) + if err != nil { + return nil, slerrors.BadRequest("Invalid value for from") + } + } + if toTime.After(toTime) { + return nil, slerrors.BadRequest("the flow of time itself is convoluted, but not so for the database") + } + } else { + if c.Query("to") == "" { + toDate, _ = models.ParseDate(time.Now().Add(time.Hour * 24 * 7).Format("2006-01-02")) + } else { + toDate, err = models.ParseDate(c.Query("to")) + if err != nil { + return nil, slerrors.BadRequest("Invalid value for to") + } + } + if c.Query("from") == "" { + fromDate, _ = models.ParseDate(time.Now().Format("2006-01-02")) + } else { + fromDate, err = models.ParseDate(c.Query("from")) + if err != nil { + return nil, slerrors.BadRequest("Invalid value for from") + } + } } - item, err := db.Items(getScope(c).ID).Find(c.Request.Context(), id) + switch mode { + case "", "created": + return db.Items(getScope(c).ID).ListCreated(c.Request.Context(), fromTime, toTime) + case "acquired": + return db.Items(getScope(c).ID).ListAcquired(c.Request.Context(), fromTime, toTime) + case "loose": + return db.Items(getScope(c).ID).ListLoose(c.Request.Context(), fromTime, toTime) + case "scheduled": + return db.Items(getScope(c).ID).ListScheduled(c.Request.Context(), fromDate, toDate) + default: + return nil, slerrors.BadRequest("unknown mode") + } + })) + + g.GET("/:id", handler("item", func(c *gin.Context) (interface{}, error) { + id, err := reqInt(c, "id") if err != nil { return nil, err } - for _, member := range getScope(c).Members { - if member.ID == item.OwnerID { - item.Owner = &member - } - } - return item, nil + return db.Items(getScope(c).ID).Find(c.Request.Context(), id) })) g.POST("/", handler("item", func(c *gin.Context) (interface{}, error) { @@ -48,10 +96,6 @@ func Items(g *gin.RouterGroup, db database.Database) { } else if getScope(c).HasMember(item.OwnerID) { return nil, slerrors.Forbidden("Owner is not part of scope.") } - if item.ProjectRequirementID != nil { - // TODO: Ensure project requirement exists in scope. - return nil, slerrors.Forbidden("TODO: check project requirement ID.") - } for _, stat := range item.Stats { if getScope(c).Stat(stat.ID) == nil { return nil, slerrors.Forbidden("One or more stats are not part of scope.") @@ -61,12 +105,61 @@ func Items(g *gin.RouterGroup, db database.Database) { } } - item, err = db.Items(getScope(c).ID).Create(c.Request.Context(), *item) + return db.Items(getScope(c).ID).Create(c.Request.Context(), *item) + })) + + g.PUT("/:id", handler("item", func(c *gin.Context) (interface{}, error) { + id, err := reqInt(c, "id") + if err != nil { + return nil, err + } + + update := &models.ItemUpdate{} + err = c.BindJSON(update) + if err != nil { + return nil, slerrors.BadRequest("Invalid JSON input: " + err.Error()) + } + + item, err := db.Items(getScope(c).ID).Find(c.Request.Context(), id) + if err != nil { + return nil, err + } + + if update.Name != nil && *update.Name == "" { + return nil, slerrors.BadRequest("Blank item name not allowed") + } + if update.OwnerID != nil || !getScope(c).HasMember(item.OwnerID) { + return nil, slerrors.Forbidden("New item is not part of scope.") + } + for _, stat := range update.Stats { + statRef := getScope(c).Stat(stat.ID) + if stat.Required != 0 && stat.Acquired != 0 && statRef == nil { + return nil, slerrors.Forbidden("One or more stats are not part of the scope.") + } + if !statRef.AllowsAmount(stat.Required) { + return nil, slerrors.Forbidden("One or more stats have a disallowed required amount.") + } + } + + return db.Items(getScope(c).ID).Update(c.Request.Context(), *item, *update) + })) + + g.DELETE("/:id", handler("item", func(c *gin.Context) (interface{}, error) { + id, err := reqInt(c, "id") + if err != nil { + return nil, err + } + + item, err := db.Items(getScope(c).ID).Find(c.Request.Context(), id) if err != nil { return nil, err } - item.Owner = getScope(c).Member(item.OwnerID) - return item, err + err = db.Items(getScope(c).ID).Delete(c.Request.Context(), *item) + if err != nil { + return nil, err + } + + return item, nil })) } diff --git a/api/projects.go b/api/projects.go new file mode 100644 index 0000000..887dcd5 --- /dev/null +++ b/api/projects.go @@ -0,0 +1,50 @@ +package api + +import ( + "git.aiterp.net/stufflog3/stufflog3-api/internal/auth" + "git.aiterp.net/stufflog3/stufflog3-api/internal/database" + "git.aiterp.net/stufflog3/stufflog3-api/internal/models" + "git.aiterp.net/stufflog3/stufflog3-api/internal/slerrors" + "github.com/gin-gonic/gin" + "time" +) + +func Projects(g *gin.RouterGroup, db database.Database) { + g.Use(scopeIDMiddleware(db)) + + g.GET("/", handler("projects", func(c *gin.Context) (interface{}, error) { + return db.Projects(getScope(c).ID).List(c.Request.Context()) + })) + + g.GET("/:id", handler("project", func(c *gin.Context) (interface{}, error) { + id, err := reqInt(c, "id") + if err != nil { + return nil, err + } + + return db.Projects(getScope(c).ID).Find(c.Request.Context(), id) + })) + + g.POST("/", handler("project", func(c *gin.Context) (interface{}, error) { + project := &models.Project{} + err := c.BindJSON(project) + if err != nil { + return nil, slerrors.BadRequest("Invalid JSON input: " + err.Error()) + } + + if !project.Status.Valid() { + return nil, slerrors.BadRequest("Unknown/unsupported project status") + } + if project.Name == "" { + return nil, slerrors.BadRequest("Project name cannot be blank") + } + if len(project.Requirements) > 0 { + return nil, slerrors.BadRequest("You cannot submit requirements this way") + } + + project.OwnerID = auth.UserID(c) + project.CreatedTime = time.Now() + + return db.Projects(getScope(c).ID).Create(c.Request.Context(), *project) + })) +} diff --git a/api/scope.go b/api/scope.go index b966e95..5a314c4 100644 --- a/api/scope.go +++ b/api/scope.go @@ -3,6 +3,7 @@ package api import ( "git.aiterp.net/stufflog3/stufflog3-api/internal/auth" "git.aiterp.net/stufflog3/stufflog3-api/internal/database" + "git.aiterp.net/stufflog3/stufflog3-api/internal/models" "git.aiterp.net/stufflog3/stufflog3-api/internal/slerrors" "github.com/gin-gonic/gin" ) @@ -25,6 +26,7 @@ func Scopes(g *gin.RouterGroup, db database.Database) { if !scope.HasMember(auth.UserID(c)) { return nil, slerrors.NotFound("Scope") } + scope.StatusLabels = models.StatusLabels return scope, nil })) diff --git a/cmd/stufflog3-server.go b/cmd/stufflog3-server.go index 5a69139..23a1d5c 100644 --- a/cmd/stufflog3-server.go +++ b/cmd/stufflog3-server.go @@ -35,6 +35,7 @@ func main() { api.Scopes(server.Group("/api/scopes"), db) api.Items(server.Group("/api/scope/:scope_id/items"), db) + api.Projects(server.Group("/api/scope/:scope_id/projects"), db) exitSignal := make(chan os.Signal) signal.Notify(exitSignal, os.Interrupt, os.Kill, syscall.SIGTERM) diff --git a/go.mod b/go.mod index e938fcf..cd33a02 100644 --- a/go.mod +++ b/go.mod @@ -8,24 +8,4 @@ require ( github.com/kyleconroy/sqlc v1.13.0 ) -require ( - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-playground/locales v0.13.0 // indirect - github.com/go-playground/universal-translator v0.17.0 // indirect - github.com/go-playground/validator/v10 v10.4.1 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/json-iterator/go v1.1.9 // indirect - github.com/kr/text v0.1.0 // indirect - github.com/leodido/go-urn v1.2.0 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect - github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect - github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect - github.com/stretchr/testify v1.7.0 // indirect - github.com/ugorji/go/codec v1.1.7 // indirect - golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect - google.golang.org/protobuf v1.28.0 // indirect - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect -) +require golang.org/x/sync v0.0.0-20210220032951-036812b2e83c diff --git a/internal/database/database.go b/internal/database/database.go index 7c068e2..6da42b1 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -10,7 +10,8 @@ type Database interface { Scopes() ScopeRepository Stats(scopeID int) StatRepository Items(scopeID int) ItemRepository - ItemsMultiScope(scopeIDs []int) ItemRepository + Projects(scopeID int) ProjectRepository + ItemsMultiScope(scopeIDs []int) ItemMultiScopeRepository } type ScopeRepository interface { @@ -52,12 +53,12 @@ type ItemMultiScopeRepository interface { type ProjectRepository interface { Find(ctx context.Context, id int) (*models.Project, error) - List(ctx context.Context) ([]models.Project, error) + List(ctx context.Context) ([]models.ProjectEntry, error) Create(ctx context.Context, project models.Project) (*models.Project, error) Update(ctx context.Context, project models.Project) (*models.Project, error) Delete(ctx context.Context, project models.ProjectEntry) error AddRequirement(ctx context.Context, project models.ProjectEntry, requirement models.ProjectRequirement) UpdateRequirement(ctx context.Context, project models.ProjectEntry, requirement models.ProjectRequirement, update models.ProjectRequirementUpdate) - DeleteRequirement(ctx context.Context, project models.ProjectEntry, requirement models.ProjectRequirement) + DeleteRequirement(ctx context.Context, project models.ProjectEntry, requirement models.ProjectRequirement, deleteItems bool) } diff --git a/internal/database/mysql/database.go b/internal/database/mysql/database.go index 44d90f7..8509f2f 100644 --- a/internal/database/mysql/database.go +++ b/internal/database/mysql/database.go @@ -1,9 +1,11 @@ package mysql import ( + "context" "database/sql" "fmt" "git.aiterp.net/stufflog3/stufflog3-api/internal/database" + "git.aiterp.net/stufflog3/stufflog3-api/internal/database/mysql/mysqlcore" "time" _ "github.com/go-sql-driver/mysql" @@ -11,10 +13,11 @@ import ( type Database struct { db *sql.DB + q *mysqlcore.Queries } func (d *Database) Scopes() database.ScopeRepository { - return &scopeRepository{db: d.db} + return &scopeRepository{db: d.db, q: d.q} } func (d *Database) Stats(scopeID int) database.StatRepository { @@ -23,10 +26,17 @@ func (d *Database) Stats(scopeID int) database.StatRepository { } func (d *Database) Items(scopeID int) database.ItemRepository { - return &itemRepository{db: d.db, scopeID: scopeID} + return &itemRepository{db: d.db, q: d.q, scopeID: scopeID} } -func (d *Database) ItemsMultiScope(scopeIDs []int) database.ItemRepository { +func (d *Database) Projects(scopeID int) database.ProjectRepository { + return &projectRepository{ + db: d.db, q: d.q, scopeID: scopeID, + items: &itemRepository{db: d.db, q: d.q, scopeID: scopeID}, + } +} + +func (d *Database) ItemsMultiScope(scopeIDs []int) database.ItemMultiScopeRepository { //TODO implement me panic("implement me") } @@ -48,5 +58,10 @@ func Connect(host string, port int, username, password, database string) (*Datab return nil, err } - return &Database{db: db}, nil + q, err := mysqlcore.Prepare(context.Background(), db) + if err != nil { + return nil, err + } + + return &Database{db: db, q: q}, nil } diff --git a/internal/database/mysql/items.go b/internal/database/mysql/items.go index ed19914..5812ea0 100644 --- a/internal/database/mysql/items.go +++ b/internal/database/mysql/items.go @@ -13,11 +13,12 @@ import ( type itemRepository struct { db *sql.DB + q *mysqlcore.Queries scopeID int } func (r *itemRepository) Find(ctx context.Context, id int) (*models.Item, error) { - res, err := mysqlcore.New(r.db).GetItem(ctx, id) + res, err := r.q.GetItem(ctx, id) if err != nil { if err == sql.ErrNoRows { return nil, slerrors.NotFound("Item") @@ -30,7 +31,7 @@ func (r *itemRepository) Find(ctx context.Context, id int) (*models.Item, error) item := r.resToItem(mysqlcore.ListItemsAcquiredBetweenRow(res)) - stats, _ := mysqlcore.New(r.db).ListItemStatProgress(ctx, id) + stats, _ := r.q.ListItemStatProgress(ctx, id) for _, stat := range stats { item.Stats = append(item.Stats, models.StatProgressEntry{ StatEntry: models.StatEntry{ @@ -47,7 +48,7 @@ func (r *itemRepository) Find(ctx context.Context, id int) (*models.Item, error) } func (r *itemRepository) ListCreated(ctx context.Context, from, to time.Time) ([]models.Item, error) { - rows, err := mysqlcore.New(r.db).ListItemsCreatedBetween(ctx, mysqlcore.ListItemsCreatedBetweenParams{ + rows, err := r.q.ListItemsCreatedBetween(ctx, mysqlcore.ListItemsCreatedBetweenParams{ CreatedTime: from, CreatedTime_2: to, ScopeID: r.scopeID, @@ -70,7 +71,7 @@ func (r *itemRepository) ListCreated(ctx context.Context, from, to time.Time) ([ } func (r *itemRepository) ListAcquired(ctx context.Context, from, to time.Time) ([]models.Item, error) { - rows, err := mysqlcore.New(r.db).ListItemsAcquiredBetween(ctx, mysqlcore.ListItemsAcquiredBetweenParams{ + rows, err := r.q.ListItemsAcquiredBetween(ctx, mysqlcore.ListItemsAcquiredBetweenParams{ AcquiredTime: sql.NullTime{Valid: true, Time: from}, AcquiredTime_2: sql.NullTime{Valid: true, Time: to}, ScopeID: r.scopeID, @@ -93,7 +94,7 @@ func (r *itemRepository) ListAcquired(ctx context.Context, from, to time.Time) ( } func (r *itemRepository) ListScheduled(ctx context.Context, from, to models.Date) ([]models.Item, error) { - rows, err := mysqlcore.New(r.db).ListItemsScheduledBetween(ctx, mysqlcore.ListItemsScheduledBetweenParams{ + rows, err := r.q.ListItemsScheduledBetween(ctx, mysqlcore.ListItemsScheduledBetweenParams{ ScheduledDate: sqltypes.NullDate{Valid: true, Date: from}, ScheduledDate_2: sqltypes.NullDate{Valid: true, Date: to}, ScopeID: r.scopeID, @@ -116,7 +117,7 @@ func (r *itemRepository) ListScheduled(ctx context.Context, from, to models.Date } func (r *itemRepository) ListLoose(ctx context.Context, from, to time.Time) ([]models.Item, error) { - rows, err := mysqlcore.New(r.db).ListItemsLooseBetween(ctx, mysqlcore.ListItemsLooseBetweenParams{ + rows, err := r.q.ListItemsLooseBetween(ctx, mysqlcore.ListItemsLooseBetweenParams{ CreatedTime: from, CreatedTime_2: to, ScopeID: r.scopeID, @@ -146,7 +147,14 @@ func (r *itemRepository) Create(ctx context.Context, item models.Item) (*models. return nil, err } defer tx.Rollback() - q := mysqlcore.New(tx) + q := r.q.WithTx(tx) + + if item.ProjectRequirementID != nil { + pr, err := q.GetProjectRequirement(ctx, *item.ProjectRequirementID) + if err != nil || pr.ScopeID != r.scopeID { + return nil, slerrors.NotFound("Project requirement") + } + } prID, acqTime, schDate := r.generateNullables(item) @@ -212,7 +220,15 @@ func (r *itemRepository) Update(ctx context.Context, item models.Item, update mo return nil, err } defer tx.Rollback() - q := mysqlcore.New(r.db) + + q := r.q.WithTx(tx) + + if update.ProjectRequirementID != nil { + pr, err := q.GetProjectRequirement(ctx, *update.ProjectRequirementID) + if err != nil || pr.ScopeID != r.scopeID { + return nil, slerrors.NotFound("Project requirement") + } + } item.ApplyUpdate(update) @@ -266,7 +282,7 @@ func (r *itemRepository) Delete(ctx context.Context, item models.Item) error { return err } defer tx.Rollback() - q := mysqlcore.New(r.db) + q := r.q.WithTx(tx) err = q.DeleteItem(ctx, item.ID) if err != nil { @@ -322,6 +338,10 @@ func (r *itemRepository) fillStats(ctx context.Context, items []models.Item) err rows, err := r.db.QueryContext(ctx, query, ids...) if err != nil { + if err == sql.ErrNoRows { + return nil + } + return err } diff --git a/internal/database/mysql/mysqlcore/db.go b/internal/database/mysql/mysqlcore/db.go index 70791b9..0c22692 100644 --- a/internal/database/mysql/mysqlcore/db.go +++ b/internal/database/mysql/mysqlcore/db.go @@ -7,6 +7,7 @@ package mysqlcore import ( "context" "database/sql" + "fmt" ) type DBTX interface { @@ -20,12 +21,498 @@ 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.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.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.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.getProjectRequirementStmt, err = db.PrepareContext(ctx, getProjectRequirement); err != nil { + return nil, fmt.Errorf("error preparing query GetProjectRequirement: %w", err) + } + if q.getScopeStmt, err = db.PrepareContext(ctx, getScope); err != nil { + return nil, fmt.Errorf("error preparing query GetScope: %w", err) + } + if q.getScopeDisplayNameStmt, err = db.PrepareContext(ctx, getScopeDisplayName); err != nil { + return nil, fmt.Errorf("error preparing query GetScopeDisplayName: %w", err) + } + if q.getScopeWithDisplayNameStmt, err = db.PrepareContext(ctx, getScopeWithDisplayName); err != nil { + return nil, fmt.Errorf("error preparing query GetScopeWithDisplayName: %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.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.listProjectEntriesStmt, err = db.PrepareContext(ctx, listProjectEntries); err != nil { + return nil, fmt.Errorf("error preparing query ListProjectEntries: %w", err) + } + if q.listProjectRequirementStatsStmt, err = db.PrepareContext(ctx, listProjectRequirementStats); err != nil { + return nil, fmt.Errorf("error preparing query ListProjectRequirementStats: %w", err) + } + if q.listProjectRequirementsByProjectIDStmt, err = db.PrepareContext(ctx, listProjectRequirementsByProjectID); err != nil { + return nil, fmt.Errorf("error preparing query ListProjectRequirementsByProjectID: %w", err) + } + if q.listScopeMembersStmt, err = db.PrepareContext(ctx, listScopeMembers); err != nil { + return nil, fmt.Errorf("error preparing query ListScopeMembers: %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.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.updateScopeMemberStmt, err = db.PrepareContext(ctx, updateScopeMember); err != nil { + return nil, fmt.Errorf("error preparing query UpdateScopeMember: %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.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.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.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.getProjectRequirementStmt != nil { + if cerr := q.getProjectRequirementStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getProjectRequirementStmt: %w", cerr) + } + } + if q.getScopeStmt != nil { + if cerr := q.getScopeStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getScopeStmt: %w", cerr) + } + } + if q.getScopeDisplayNameStmt != nil { + if cerr := q.getScopeDisplayNameStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getScopeDisplayNameStmt: %w", cerr) + } + } + if q.getScopeWithDisplayNameStmt != nil { + if cerr := q.getScopeWithDisplayNameStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing getScopeWithDisplayNameStmt: %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.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.listProjectEntriesStmt != nil { + if cerr := q.listProjectEntriesStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listProjectEntriesStmt: %w", cerr) + } + } + if q.listProjectRequirementStatsStmt != nil { + if cerr := q.listProjectRequirementStatsStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listProjectRequirementStatsStmt: %w", cerr) + } + } + if q.listProjectRequirementsByProjectIDStmt != nil { + if cerr := q.listProjectRequirementsByProjectIDStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listProjectRequirementsByProjectIDStmt: %w", cerr) + } + } + if q.listScopeMembersStmt != nil { + if cerr := q.listScopeMembersStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing listScopeMembersStmt: %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.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.updateScopeMemberStmt != nil { + if cerr := q.updateScopeMemberStmt.Close(); cerr != nil { + err = fmt.Errorf("error closing updateScopeMemberStmt: %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 + db DBTX + tx *sql.Tx + clearItemProjectRequirementStmt *sql.Stmt + clearItemStatProgressStmt *sql.Stmt + deleteAllScopeMembersStmt *sql.Stmt + deleteItemStmt *sql.Stmt + deleteItemForRequirementStmt *sql.Stmt + deleteItemStatProgressStmt *sql.Stmt + deleteProjectStmt *sql.Stmt + deleteProjectRequirementStmt *sql.Stmt + deleteScopeStmt *sql.Stmt + deleteScopeMemberStmt *sql.Stmt + getItemStmt *sql.Stmt + getItemStatProgressBetweenStmt *sql.Stmt + getProjectStmt *sql.Stmt + getProjectRequirementStmt *sql.Stmt + getScopeStmt *sql.Stmt + getScopeDisplayNameStmt *sql.Stmt + getScopeWithDisplayNameStmt *sql.Stmt + insertItemStmt *sql.Stmt + insertProjectStmt *sql.Stmt + insertProjectRequirementStmt *sql.Stmt + insertScopeStmt *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 + listProjectEntriesStmt *sql.Stmt + listProjectRequirementStatsStmt *sql.Stmt + listProjectRequirementsByProjectIDStmt *sql.Stmt + listScopeMembersStmt *sql.Stmt + listScopesStmt *sql.Stmt + listScopesByUserStmt *sql.Stmt + listStatsStmt *sql.Stmt + replaceItemStatProgressStmt *sql.Stmt + updateItemStmt *sql.Stmt + updateProjectStmt *sql.Stmt + updateProjectRequirementStmt *sql.Stmt + updateScopeStmt *sql.Stmt + updateScopeMemberStmt *sql.Stmt } func (q *Queries) WithTx(tx *sql.Tx) *Queries { return &Queries{ - db: tx, + db: tx, + tx: tx, + clearItemProjectRequirementStmt: q.clearItemProjectRequirementStmt, + clearItemStatProgressStmt: q.clearItemStatProgressStmt, + deleteAllScopeMembersStmt: q.deleteAllScopeMembersStmt, + deleteItemStmt: q.deleteItemStmt, + deleteItemForRequirementStmt: q.deleteItemForRequirementStmt, + deleteItemStatProgressStmt: q.deleteItemStatProgressStmt, + deleteProjectStmt: q.deleteProjectStmt, + deleteProjectRequirementStmt: q.deleteProjectRequirementStmt, + deleteScopeStmt: q.deleteScopeStmt, + deleteScopeMemberStmt: q.deleteScopeMemberStmt, + getItemStmt: q.getItemStmt, + getItemStatProgressBetweenStmt: q.getItemStatProgressBetweenStmt, + getProjectStmt: q.getProjectStmt, + getProjectRequirementStmt: q.getProjectRequirementStmt, + getScopeStmt: q.getScopeStmt, + getScopeDisplayNameStmt: q.getScopeDisplayNameStmt, + getScopeWithDisplayNameStmt: q.getScopeWithDisplayNameStmt, + insertItemStmt: q.insertItemStmt, + insertProjectStmt: q.insertProjectStmt, + insertProjectRequirementStmt: q.insertProjectRequirementStmt, + insertScopeStmt: q.insertScopeStmt, + 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, + listProjectEntriesStmt: q.listProjectEntriesStmt, + listProjectRequirementStatsStmt: q.listProjectRequirementStatsStmt, + listProjectRequirementsByProjectIDStmt: q.listProjectRequirementsByProjectIDStmt, + listScopeMembersStmt: q.listScopeMembersStmt, + listScopesStmt: q.listScopesStmt, + listScopesByUserStmt: q.listScopesByUserStmt, + listStatsStmt: q.listStatsStmt, + replaceItemStatProgressStmt: q.replaceItemStatProgressStmt, + updateItemStmt: q.updateItemStmt, + updateProjectStmt: q.updateProjectStmt, + updateProjectRequirementStmt: q.updateProjectRequirementStmt, + updateScopeStmt: q.updateScopeStmt, + updateScopeMemberStmt: q.updateScopeMemberStmt, } } diff --git a/internal/database/mysql/mysqlcore/item.sql.go b/internal/database/mysql/mysqlcore/item.sql.go index c757dae..2f7e726 100644 --- a/internal/database/mysql/mysqlcore/item.sql.go +++ b/internal/database/mysql/mysqlcore/item.sql.go @@ -13,12 +13,21 @@ import ( "git.aiterp.net/stufflog3/stufflog3-api/internal/sqltypes" ) +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 clearItemStatProgress = `-- name: ClearItemStatProgress :exec DELETE FROM item_stat_progress WHERE item_id = ? ` func (q *Queries) ClearItemStatProgress(ctx context.Context, itemID int) error { - _, err := q.db.ExecContext(ctx, clearItemStatProgress, itemID) + _, err := q.exec(ctx, q.clearItemStatProgressStmt, clearItemStatProgress, itemID) return err } @@ -27,7 +36,16 @@ DELETE FROM item WHERE id = ? ` func (q *Queries) DeleteItem(ctx context.Context, id int) error { - _, err := q.db.ExecContext(ctx, deleteItem, id) + _, 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 } @@ -41,7 +59,7 @@ type DeleteItemStatProgressParams struct { } func (q *Queries) DeleteItemStatProgress(ctx context.Context, arg DeleteItemStatProgressParams) error { - _, err := q.db.ExecContext(ctx, deleteItemStatProgress, arg.ItemID, arg.StatID) + _, err := q.exec(ctx, q.deleteItemStatProgressStmt, deleteItemStatProgress, arg.ItemID, arg.StatID) return err } @@ -65,7 +83,7 @@ type GetItemRow struct { } func (q *Queries) GetItem(ctx context.Context, id int) (GetItemRow, error) { - row := q.db.QueryRowContext(ctx, getItem, id) + row := q.queryRow(ctx, q.getItemStmt, getItem, id) var i GetItemRow err := row.Scan( &i.ID, @@ -107,7 +125,7 @@ type GetItemStatProgressBetweenRow struct { } func (q *Queries) GetItemStatProgressBetween(ctx context.Context, arg GetItemStatProgressBetweenParams) (GetItemStatProgressBetweenRow, error) { - row := q.db.QueryRowContext(ctx, getItemStatProgressBetween, arg.AcquiredTime, arg.AcquiredTime_2, arg.ScopeID) + row := q.queryRow(ctx, q.getItemStatProgressBetweenStmt, getItemStatProgressBetween, arg.AcquiredTime, arg.AcquiredTime_2, arg.ScopeID) var i GetItemStatProgressBetweenRow err := row.Scan( &i.ID, @@ -136,7 +154,7 @@ type InsertItemParams struct { } func (q *Queries) InsertItem(ctx context.Context, arg InsertItemParams) (sql.Result, error) { - return q.db.ExecContext(ctx, insertItem, + return q.exec(ctx, q.insertItemStmt, insertItem, arg.ScopeID, arg.ProjectRequirementID, arg.Name, @@ -163,7 +181,7 @@ type ListItemStatProgressRow struct { } func (q *Queries) ListItemStatProgress(ctx context.Context, itemID int) ([]ListItemStatProgressRow, error) { - rows, err := q.db.QueryContext(ctx, listItemStatProgress, itemID) + rows, err := q.query(ctx, q.listItemStatProgressStmt, listItemStatProgress, itemID) if err != nil { return nil, err } @@ -218,7 +236,7 @@ type ListItemStatProgressMultiRow struct { } func (q *Queries) ListItemStatProgressMulti(ctx context.Context, arg ListItemStatProgressMultiParams) ([]ListItemStatProgressMultiRow, error) { - rows, err := q.db.QueryContext(ctx, listItemStatProgressMulti, + rows, err := q.query(ctx, q.listItemStatProgressMultiStmt, listItemStatProgressMulti, arg.ItemID, arg.ItemID_2, arg.ItemID_3, @@ -260,8 +278,9 @@ const listItemsAcquiredBetween = `-- name: ListItemsAcquiredBetween :many SELECT i.id, i.scope_id, i.project_requirement_id, i.name, i.description, i.created_time, i.created_user_id, 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.acquired_time <= ? AND i.scope_id = ? +ORDER BY acquired_time DESC, created_time DESC ` type ListItemsAcquiredBetweenParams struct { @@ -284,7 +303,7 @@ type ListItemsAcquiredBetweenRow struct { } func (q *Queries) ListItemsAcquiredBetween(ctx context.Context, arg ListItemsAcquiredBetweenParams) ([]ListItemsAcquiredBetweenRow, error) { - rows, err := q.db.QueryContext(ctx, listItemsAcquiredBetween, arg.AcquiredTime, arg.AcquiredTime_2, arg.ScopeID) + rows, err := q.query(ctx, q.listItemsAcquiredBetweenStmt, listItemsAcquiredBetween, arg.AcquiredTime, arg.AcquiredTime_2, arg.ScopeID) if err != nil { return nil, err } @@ -337,7 +356,7 @@ type ListItemsByProjectRow struct { } func (q *Queries) ListItemsByProject(ctx context.Context, projectID int) ([]ListItemsByProjectRow, error) { - rows, err := q.db.QueryContext(ctx, listItemsByProject, projectID) + rows, err := q.query(ctx, q.listItemsByProjectStmt, listItemsByProject, projectID) if err != nil { return nil, err } @@ -374,8 +393,9 @@ const listItemsCreatedBetween = `-- name: ListItemsCreatedBetween :many SELECT i.id, i.scope_id, i.project_requirement_id, i.name, i.description, i.created_time, i.created_user_id, 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.created_time <= ? AND i.scope_id = ? +ORDER BY created_time DESC ` type ListItemsCreatedBetweenParams struct { @@ -398,7 +418,7 @@ type ListItemsCreatedBetweenRow struct { } func (q *Queries) ListItemsCreatedBetween(ctx context.Context, arg ListItemsCreatedBetweenParams) ([]ListItemsCreatedBetweenRow, error) { - rows, err := q.db.QueryContext(ctx, listItemsCreatedBetween, arg.CreatedTime, arg.CreatedTime_2, arg.ScopeID) + rows, err := q.query(ctx, q.listItemsCreatedBetweenStmt, listItemsCreatedBetween, arg.CreatedTime, arg.CreatedTime_2, arg.ScopeID) if err != nil { return nil, err } @@ -457,7 +477,7 @@ type ListItemsCreatedBetweenNoScopeRow struct { } func (q *Queries) ListItemsCreatedBetweenNoScope(ctx context.Context, arg ListItemsCreatedBetweenNoScopeParams) ([]ListItemsCreatedBetweenNoScopeRow, error) { - rows, err := q.db.QueryContext(ctx, listItemsCreatedBetweenNoScope, arg.CreatedTime, arg.CreatedTime_2) + rows, err := q.query(ctx, q.listItemsCreatedBetweenNoScopeStmt, listItemsCreatedBetweenNoScope, arg.CreatedTime, arg.CreatedTime_2) if err != nil { return nil, err } @@ -494,10 +514,12 @@ const listItemsLooseBetween = `-- name: ListItemsLooseBetween :many SELECT i.id, i.scope_id, i.project_requirement_id, i.name, i.description, i.created_time, i.created_user_id, 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.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 { @@ -520,7 +542,7 @@ type ListItemsLooseBetweenRow struct { } func (q *Queries) ListItemsLooseBetween(ctx context.Context, arg ListItemsLooseBetweenParams) ([]ListItemsLooseBetweenRow, error) { - rows, err := q.db.QueryContext(ctx, listItemsLooseBetween, arg.CreatedTime, arg.CreatedTime_2, arg.ScopeID) + rows, err := q.query(ctx, q.listItemsLooseBetweenStmt, listItemsLooseBetween, arg.CreatedTime, arg.CreatedTime_2, arg.ScopeID) if err != nil { return nil, err } @@ -581,7 +603,7 @@ type ListItemsLooseBetweenNoScopeRow struct { } func (q *Queries) ListItemsLooseBetweenNoScope(ctx context.Context, arg ListItemsLooseBetweenNoScopeParams) ([]ListItemsLooseBetweenNoScopeRow, error) { - rows, err := q.db.QueryContext(ctx, listItemsLooseBetweenNoScope, arg.CreatedTime, arg.CreatedTime_2) + rows, err := q.query(ctx, q.listItemsLooseBetweenNoScopeStmt, listItemsLooseBetweenNoScope, arg.CreatedTime, arg.CreatedTime_2) if err != nil { return nil, err } @@ -618,8 +640,9 @@ const listItemsScheduledBetween = `-- name: ListItemsScheduledBetween :many SELECT i.id, i.scope_id, i.project_requirement_id, i.name, i.description, i.created_time, i.created_user_id, 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.scheduled_date <= ? AND i.scope_id = ? +ORDER BY scheduled_date, created_time ` type ListItemsScheduledBetweenParams struct { @@ -642,7 +665,7 @@ type ListItemsScheduledBetweenRow struct { } func (q *Queries) ListItemsScheduledBetween(ctx context.Context, arg ListItemsScheduledBetweenParams) ([]ListItemsScheduledBetweenRow, error) { - rows, err := q.db.QueryContext(ctx, listItemsScheduledBetween, arg.ScheduledDate, arg.ScheduledDate_2, arg.ScopeID) + rows, err := q.query(ctx, q.listItemsScheduledBetweenStmt, listItemsScheduledBetween, arg.ScheduledDate, arg.ScheduledDate_2, arg.ScopeID) if err != nil { return nil, err } @@ -701,7 +724,7 @@ type ListItemsScheduledBetweenNoScopeRow struct { } func (q *Queries) ListItemsScheduledBetweenNoScope(ctx context.Context, arg ListItemsScheduledBetweenNoScopeParams) ([]ListItemsScheduledBetweenNoScopeRow, error) { - rows, err := q.db.QueryContext(ctx, listItemsScheduledBetweenNoScope, arg.ScheduledDate, arg.ScheduledDate_2) + rows, err := q.query(ctx, q.listItemsScheduledBetweenNoScopeStmt, listItemsScheduledBetweenNoScope, arg.ScheduledDate, arg.ScheduledDate_2) if err != nil { return nil, err } @@ -747,7 +770,7 @@ type ReplaceItemStatProgressParams struct { } func (q *Queries) ReplaceItemStatProgress(ctx context.Context, arg ReplaceItemStatProgressParams) error { - _, err := q.db.ExecContext(ctx, replaceItemStatProgress, + _, err := q.exec(ctx, q.replaceItemStatProgressStmt, replaceItemStatProgress, arg.ItemID, arg.StatID, arg.Acquired, @@ -778,7 +801,7 @@ type UpdateItemParams struct { } func (q *Queries) UpdateItem(ctx context.Context, arg UpdateItemParams) error { - _, err := q.db.ExecContext(ctx, updateItem, + _, err := q.exec(ctx, q.updateItemStmt, updateItem, arg.ProjectRequirementID, arg.Name, arg.Description, diff --git a/internal/database/mysql/mysqlcore/project.sql.go b/internal/database/mysql/mysqlcore/project.sql.go index 335be18..9becf3d 100644 --- a/internal/database/mysql/mysqlcore/project.sql.go +++ b/internal/database/mysql/mysqlcore/project.sql.go @@ -7,14 +7,34 @@ package mysqlcore import ( "context" + "database/sql" + "time" ) +const deleteProject = `-- name: DeleteProject :exec +DELETE FROM project WHERE id = ? +` + +func (q *Queries) DeleteProject(ctx context.Context, id int) error { + _, err := q.exec(ctx, q.deleteProjectStmt, deleteProject, id) + return err +} + +const deleteProjectRequirement = `-- name: DeleteProjectRequirement :exec +DELETE FROM project_requirement WHERE id = ? +` + +func (q *Queries) DeleteProjectRequirement(ctx context.Context, id int) error { + _, err := q.exec(ctx, q.deleteProjectRequirementStmt, deleteProjectRequirement, id) + return err +} + const getProject = `-- name: GetProject :one SELECT id, scope_id, author_id, name, status, description, created_time FROM project WHERE id = ? ` func (q *Queries) GetProject(ctx context.Context, id int) (Project, error) { - row := q.db.QueryRowContext(ctx, getProject, id) + row := q.queryRow(ctx, q.getProjectStmt, getProject, id) var i Project err := row.Scan( &i.ID, @@ -28,20 +48,88 @@ func (q *Queries) GetProject(ctx context.Context, id int) (Project, error) { return i, err } +const getProjectRequirement = `-- name: GetProjectRequirement :one +SELECT id, scope_id, project_id, name, status, description FROM project_requirement WHERE id = ? +` + +func (q *Queries) GetProjectRequirement(ctx context.Context, id int) (ProjectRequirement, error) { + row := q.queryRow(ctx, q.getProjectRequirementStmt, getProjectRequirement, id) + var i ProjectRequirement + err := row.Scan( + &i.ID, + &i.ScopeID, + &i.ProjectID, + &i.Name, + &i.Status, + &i.Description, + ) + return i, err +} + +const insertProject = `-- name: InsertProject :execresult +INSERT INTO project (scope_id, author_id, name, status, description, created_time) +VALUES (?, ?, ?, ?, ?, ?) +` + +type InsertProjectParams struct { + ScopeID int + AuthorID string + Name string + Status int + Description string + CreatedTime time.Time +} + +func (q *Queries) InsertProject(ctx context.Context, arg InsertProjectParams) (sql.Result, error) { + return q.exec(ctx, q.insertProjectStmt, insertProject, + arg.ScopeID, + arg.AuthorID, + arg.Name, + arg.Status, + arg.Description, + arg.CreatedTime, + ) +} + +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 listProjectEntries = `-- name: ListProjectEntries :many -SELECT id, name, status FROM project +SELECT id, name, status, created_time, author_id FROM project WHERE scope_id = ? ORDER BY status, created_time ` type ListProjectEntriesRow struct { - ID int - Name string - Status int + ID int + Name string + Status int + CreatedTime time.Time + AuthorID string } func (q *Queries) ListProjectEntries(ctx context.Context, scopeID int) ([]ListProjectEntriesRow, error) { - rows, err := q.db.QueryContext(ctx, listProjectEntries, scopeID) + rows, err := q.query(ctx, q.listProjectEntriesStmt, listProjectEntries, scopeID) if err != nil { return nil, err } @@ -49,7 +137,54 @@ func (q *Queries) ListProjectEntries(ctx context.Context, scopeID int) ([]ListPr items := []ListProjectEntriesRow{} for rows.Next() { var i ListProjectEntriesRow - if err := rows.Scan(&i.ID, &i.Name, &i.Status); err != nil { + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Status, + &i.CreatedTime, + &i.AuthorID, + ); 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 listProjectRequirementStats = `-- name: ListProjectRequirementStats :many +SELECT prs.required, s.id, s.name, s.weight FROM project_requirement_stat prs +RIGHT JOIN stat s ON s.id = prs.stat_id +WHERE project_requirement_id = ? +` + +type ListProjectRequirementStatsRow struct { + Required sql.NullInt32 + ID int + Name string + Weight float64 +} + +func (q *Queries) ListProjectRequirementStats(ctx context.Context, projectRequirementID int) ([]ListProjectRequirementStatsRow, error) { + rows, err := q.query(ctx, q.listProjectRequirementStatsStmt, listProjectRequirementStats, projectRequirementID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []ListProjectRequirementStatsRow{} + for rows.Next() { + var i ListProjectRequirementStatsRow + if err := rows.Scan( + &i.Required, + &i.ID, + &i.Name, + &i.Weight, + ); err != nil { return nil, err } items = append(items, i) @@ -68,7 +203,7 @@ SELECT id, scope_id, project_id, name, status, description FROM project_requirem ` func (q *Queries) ListProjectRequirementsByProjectID(ctx context.Context, projectID int) ([]ProjectRequirement, error) { - rows, err := q.db.QueryContext(ctx, listProjectRequirementsByProjectID, projectID) + rows, err := q.query(ctx, q.listProjectRequirementsByProjectIDStmt, listProjectRequirementsByProjectID, projectID) if err != nil { return nil, err } @@ -96,3 +231,53 @@ func (q *Queries) ListProjectRequirementsByProjectID(ctx context.Context, projec } return items, nil } + +const updateProject = `-- name: UpdateProject :exec +UPDATE project +SET name = ?, + status = ?, + description = ? +WHERE id = ? +` + +type UpdateProjectParams struct { + Name string + Status int + Description string + ID int +} + +func (q *Queries) UpdateProject(ctx context.Context, arg UpdateProjectParams) error { + _, err := q.exec(ctx, q.updateProjectStmt, updateProject, + arg.Name, + arg.Status, + arg.Description, + arg.ID, + ) + return err +} + +const updateProjectRequirement = `-- name: UpdateProjectRequirement :exec +UPDATE project_requirement +SET name = ?, + status = ?, + description = ? +WHERE id = ? +` + +type UpdateProjectRequirementParams struct { + Name string + Status int + Description string + ID 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, + ) + return err +} diff --git a/internal/database/mysql/mysqlcore/scope.sql.go b/internal/database/mysql/mysqlcore/scope.sql.go index 03b209d..02520c9 100644 --- a/internal/database/mysql/mysqlcore/scope.sql.go +++ b/internal/database/mysql/mysqlcore/scope.sql.go @@ -15,7 +15,7 @@ DELETE FROM scope_member WHERE scope_id=? ` func (q *Queries) DeleteAllScopeMembers(ctx context.Context, scopeID int) error { - _, err := q.db.ExecContext(ctx, deleteAllScopeMembers, scopeID) + _, err := q.exec(ctx, q.deleteAllScopeMembersStmt, deleteAllScopeMembers, scopeID) return err } @@ -24,7 +24,7 @@ DELETE FROM scope WHERE id=? ` func (q *Queries) DeleteScope(ctx context.Context, id int) error { - _, err := q.db.ExecContext(ctx, deleteScope, id) + _, err := q.exec(ctx, q.deleteScopeStmt, deleteScope, id) return err } @@ -38,7 +38,7 @@ type DeleteScopeMemberParams struct { } func (q *Queries) DeleteScopeMember(ctx context.Context, arg DeleteScopeMemberParams) error { - _, err := q.db.ExecContext(ctx, deleteScopeMember, arg.ScopeID, arg.UserID) + _, err := q.exec(ctx, q.deleteScopeMemberStmt, deleteScopeMember, arg.ScopeID, arg.UserID) return err } @@ -48,7 +48,7 @@ WHERE id = ? ` func (q *Queries) GetScope(ctx context.Context, id int) (Scope, error) { - row := q.db.QueryRowContext(ctx, getScope, id) + row := q.queryRow(ctx, q.getScopeStmt, getScope, id) var i Scope err := row.Scan(&i.ID, &i.Name, &i.Abbreviation) return i, err @@ -65,7 +65,7 @@ type GetScopeDisplayNameParams struct { } func (q *Queries) GetScopeDisplayName(ctx context.Context, arg GetScopeDisplayNameParams) (string, error) { - row := q.db.QueryRowContext(ctx, getScopeDisplayName, arg.UserID, arg.ScopeID) + row := q.queryRow(ctx, q.getScopeDisplayNameStmt, getScopeDisplayName, arg.UserID, arg.ScopeID) var name string err := row.Scan(&name) return name, err @@ -90,7 +90,7 @@ type GetScopeWithDisplayNameRow struct { } func (q *Queries) GetScopeWithDisplayName(ctx context.Context, arg GetScopeWithDisplayNameParams) (GetScopeWithDisplayNameRow, error) { - row := q.db.QueryRowContext(ctx, getScopeWithDisplayName, arg.ID, arg.UserID) + row := q.queryRow(ctx, q.getScopeWithDisplayNameStmt, getScopeWithDisplayName, arg.ID, arg.UserID) var i GetScopeWithDisplayNameRow err := row.Scan( &i.ID, @@ -113,7 +113,7 @@ type InsertScopeParams struct { } func (q *Queries) InsertScope(ctx context.Context, arg InsertScopeParams) (sql.Result, error) { - return q.db.ExecContext(ctx, insertScope, arg.ID, arg.Name, arg.Abbreviation) + return q.exec(ctx, q.insertScopeStmt, insertScope, arg.ID, arg.Name, arg.Abbreviation) } const listScopeMembers = `-- name: ListScopeMembers :many @@ -129,7 +129,7 @@ type ListScopeMembersRow struct { } func (q *Queries) ListScopeMembers(ctx context.Context, scopeID int) ([]ListScopeMembersRow, error) { - rows, err := q.db.QueryContext(ctx, listScopeMembers, scopeID) + rows, err := q.query(ctx, q.listScopeMembersStmt, listScopeMembers, scopeID) if err != nil { return nil, err } @@ -157,7 +157,7 @@ ORDER BY name ` func (q *Queries) ListScopes(ctx context.Context) ([]Scope, error) { - rows, err := q.db.QueryContext(ctx, listScopes) + rows, err := q.query(ctx, q.listScopesStmt, listScopes) if err != nil { return nil, err } @@ -194,7 +194,7 @@ type ListScopesByUserRow struct { } func (q *Queries) ListScopesByUser(ctx context.Context, userID string) ([]ListScopesByUserRow, error) { - rows, err := q.db.QueryContext(ctx, listScopesByUser, userID) + rows, err := q.query(ctx, q.listScopesByUserStmt, listScopesByUser, userID) if err != nil { return nil, err } @@ -232,7 +232,7 @@ type UpdateScopeParams struct { } func (q *Queries) UpdateScope(ctx context.Context, arg UpdateScopeParams) error { - _, err := q.db.ExecContext(ctx, updateScope, arg.Name, arg.Abbreviation, arg.ID) + _, err := q.exec(ctx, q.updateScopeStmt, updateScope, arg.Name, arg.Abbreviation, arg.ID) return err } @@ -249,7 +249,7 @@ type UpdateScopeMemberParams struct { } func (q *Queries) UpdateScopeMember(ctx context.Context, arg UpdateScopeMemberParams) error { - _, err := q.db.ExecContext(ctx, updateScopeMember, + _, err := q.exec(ctx, q.updateScopeMemberStmt, updateScopeMember, arg.ScopeID, arg.UserID, arg.Name, diff --git a/internal/database/mysql/mysqlcore/stats.sql.go b/internal/database/mysql/mysqlcore/stats.sql.go index 4d414c8..0db08ff 100644 --- a/internal/database/mysql/mysqlcore/stats.sql.go +++ b/internal/database/mysql/mysqlcore/stats.sql.go @@ -25,7 +25,7 @@ type ListStatsRow struct { } func (q *Queries) ListStats(ctx context.Context, scopeID int) ([]ListStatsRow, error) { - rows, err := q.db.QueryContext(ctx, listStats, scopeID) + rows, err := q.query(ctx, q.listStatsStmt, listStats, scopeID) if err != nil { return nil, err } diff --git a/internal/database/mysql/project.go b/internal/database/mysql/project.go new file mode 100644 index 0000000..03d43ff --- /dev/null +++ b/internal/database/mysql/project.go @@ -0,0 +1,180 @@ +package mysql + +import ( + "context" + "database/sql" + "git.aiterp.net/stufflog3/stufflog3-api/internal/database/mysql/mysqlcore" + "git.aiterp.net/stufflog3/stufflog3-api/internal/models" + "git.aiterp.net/stufflog3/stufflog3-api/internal/slerrors" + "golang.org/x/sync/errgroup" +) + +type projectRepository struct { + db *sql.DB + q *mysqlcore.Queries + items *itemRepository + scopeID int +} + +func (r *projectRepository) Find(ctx context.Context, id int) (*models.Project, error) { + row, err := r.q.GetProject(ctx, id) + if err == sql.ErrNoRows || row.ScopeID != r.scopeID { + return nil, slerrors.NotFound("Project") + } else if err != nil { + return nil, err + } + + project := models.Project{ + ProjectEntry: models.ProjectEntry{ + ID: row.ID, + OwnerID: row.AuthorID, + CreatedTime: row.CreatedTime, + Name: row.Name, + Status: models.Status(row.Status), + }, + Description: row.Description, + Requirements: []models.ProjectRequirement{}, + } + + reqs, err := r.q.ListProjectRequirementsByProjectID(ctx, id) + if err != nil && err != sql.ErrNoRows { + return nil, err + } + itemRows, err := r.q.ListItemsByProject(ctx, id) + if err != nil && err != sql.ErrNoRows { + return nil, err + } + items := make([]models.Item, 0, len(itemRows)) + for _, itemRow := range itemRows { + item := r.items.resToItem(mysqlcore.ListItemsAcquiredBetweenRow(itemRow)) + items = append(items, item) + } + + err = r.items.fillStats(ctx, items) + if err != nil { + return nil, err + } + + eg, ctx := errgroup.WithContext(ctx) + + for i := range reqs { + project.Requirements = append(project.Requirements, models.ProjectRequirement{ + ID: reqs[i].ID, + Name: reqs[i].Name, + Description: reqs[i].Description, + Status: models.Status(reqs[i].Status), + Stats: []models.StatProgressEntry{}, + Items: nil, + }) + requirement := &project.Requirements[len(project.Requirements)-1] + for _, item := range items { + if *item.ProjectRequirementID == requirement.ID { + requirement.Items = append(requirement.Items, item) + } + } + + eg.Go(func() error { + stats, err := r.q.ListProjectRequirementStats(ctx, requirement.ID) + if err != nil && err != sql.ErrNoRows { + return err + } + + for _, statRow := range stats { + stat := models.StatProgressEntry{ + StatEntry: models.StatEntry{ + ID: statRow.ID, + Name: statRow.Name, + Weight: statRow.Weight, + }, + Acquired: 0, + Required: int(statRow.Required.Int32), + } + + for _, item := range requirement.Items { + for _, stat2 := range item.Stats { + if stat2.ID == stat.ID { + stat.Acquired += stat2.Acquired + } + } + } + + requirement.Stats = append(requirement.Stats, stat) + } + + return nil + }) + } + + err = eg.Wait() + if err != nil { + return nil, err + } + + return &project, nil +} + +func (r *projectRepository) List(ctx context.Context) ([]models.ProjectEntry, error) { + rows, err := r.q.ListProjectEntries(ctx, r.scopeID) + if err != nil && err != sql.ErrNoRows { + return nil, err + } + + projects := make([]models.ProjectEntry, 0, len(rows)) + for _, row := range rows { + projects = append(projects, models.ProjectEntry{ + ID: row.ID, + OwnerID: row.AuthorID, + CreatedTime: row.CreatedTime, + Name: row.Name, + Status: models.Status(row.Status), + }) + } + + return projects, nil +} + +func (r *projectRepository) Create(ctx context.Context, project models.Project) (*models.Project, error) { + res, err := r.q.InsertProject(ctx, mysqlcore.InsertProjectParams{ + ScopeID: r.scopeID, + AuthorID: project.OwnerID, + Name: project.Name, + Status: int(project.Status), + Description: project.Description, + CreatedTime: project.CreatedTime, + }) + if err != nil { + return nil, err + } + + id, err := res.LastInsertId() + if err != nil { + return nil, err + } + + return r.Find(ctx, int(id)) +} + +func (r *projectRepository) Update(ctx context.Context, project models.Project) (*models.Project, error) { + //TODO implement me + panic("implement me") +} + +func (r *projectRepository) Delete(ctx context.Context, project models.ProjectEntry) error { + //TODO implement me + panic("implement me") +} + +func (r *projectRepository) AddRequirement(ctx context.Context, project models.ProjectEntry, requirement models.ProjectRequirement) { + //TODO implement me + panic("implement me") +} + +func (r *projectRepository) UpdateRequirement(ctx context.Context, project models.ProjectEntry, requirement models.ProjectRequirement, update models.ProjectRequirementUpdate) { + //TODO implement me + panic("implement me") +} + +func (r *projectRepository) DeleteRequirement(ctx context.Context, project models.ProjectEntry, requirement models.ProjectRequirement, deleteItems bool) { + //TODO implement me + panic("implement me") +} diff --git a/internal/database/mysql/queries/item.sql b/internal/database/mysql/queries/item.sql index d52a2f4..4925f5d 100644 --- a/internal/database/mysql/queries/item.sql +++ b/internal/database/mysql/queries/item.sql @@ -12,31 +12,36 @@ WHERE pr.project_id = ?; 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 = ?; + 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 = ?; + 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 = ?; + 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.created_time <= ? AND i.scope_id = ? AND i.scheduled_date IS NULL - AND i.acquired_time 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 @@ -94,6 +99,12 @@ WHERE id = ?; -- name: DeleteItem :exec DELETE FROM item WHERE id = ?; +-- name: DeleteItemForRequirement :exec +DELETE FROM item WHERE project_requirement_id = ?; + +-- name: ClearItemProjectRequirement :exec +UPDATE item SET project_requirement_id = NULL WHERE project_requirement_id = ?; + -- name: ReplaceItemStatProgress :exec REPLACE INTO item_stat_progress (item_id, stat_id, acquired, required) VALUES (?, ?, ?, ?); diff --git a/internal/database/mysql/queries/project.sql b/internal/database/mysql/queries/project.sql index 252298a..7f71917 100644 --- a/internal/database/mysql/queries/project.sql +++ b/internal/database/mysql/queries/project.sql @@ -1,11 +1,48 @@ -- name: ListProjectEntries :many -SELECT id, name, status FROM project +SELECT id, name, status, created_time, author_id FROM project WHERE scope_id = ? ORDER BY status, created_time; -- name: GetProject :one SELECT * FROM project WHERE id = ?; +-- name: GetProjectRequirement :one +SELECT * FROM project_requirement WHERE id = ?; + -- name: ListProjectRequirementsByProjectID :many SELECT * FROM project_requirement WHERE project_id = ?; +-- name: ListProjectRequirementStats :many +SELECT prs.required, s.id, s.name, s.weight FROM project_requirement_stat prs +RIGHT JOIN stat s ON s.id = prs.stat_id +WHERE project_requirement_id = ?; + +-- name: InsertProject :execresult +INSERT INTO project (scope_id, author_id, name, status, description, created_time) +VALUES (?, ?, ?, ?, ?, ?); + +-- name: UpdateProject :exec +UPDATE project +SET name = ?, + status = ?, + description = ? +WHERE id = ?; + +-- name: DeleteProject :exec +DELETE FROM project WHERE 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 = ?; + +-- name: DeleteProjectRequirement :exec +DELETE FROM project_requirement WHERE id = ?; + + diff --git a/internal/database/mysql/scopes.go b/internal/database/mysql/scopes.go index c16961e..0e83d84 100644 --- a/internal/database/mysql/scopes.go +++ b/internal/database/mysql/scopes.go @@ -4,21 +4,20 @@ import ( "context" "database/sql" "encoding/json" - "golang.org/x/sync/errgroup" - "sort" - "git.aiterp.net/stufflog3/stufflog3-api/internal/database/mysql/mysqlcore" "git.aiterp.net/stufflog3/stufflog3-api/internal/models" "git.aiterp.net/stufflog3/stufflog3-api/internal/slerrors" + "golang.org/x/sync/errgroup" + "sort" ) type scopeRepository struct { db *sql.DB + q *mysqlcore.Queries } func (r *scopeRepository) Find(ctx context.Context, id int, full bool) (*models.Scope, error) { - q := mysqlcore.New(r.db) - scope, err := q.GetScope(ctx, id) + scope, err := r.q.GetScope(ctx, id) if err != nil { if err == sql.ErrNoRows { return nil, slerrors.NotFound("Scope") @@ -39,16 +38,18 @@ func (r *scopeRepository) Find(ctx context.Context, id int, full bool) (*models. if full { eg.Go(func() error { - projects, err := q.ListProjectEntries(ctx, id) + projects, err := r.q.ListProjectEntries(ctx, id) if err != nil && err != sql.ErrNoRows { return err } for _, project := range projects { res.Projects = append(res.Projects, models.ProjectEntry{ - ID: project.ID, - Name: project.Name, - Status: models.Status(project.Status), + ID: project.ID, + OwnerID: project.AuthorID, + CreatedTime: project.CreatedTime, + Name: project.Name, + Status: models.Status(project.Status), }) } @@ -56,7 +57,7 @@ func (r *scopeRepository) Find(ctx context.Context, id int, full bool) (*models. }) eg.Go(func() error { - stats, err := q.ListStats(ctx, id) + stats, err := r.q.ListStats(ctx, id) if err != nil && err != sql.ErrNoRows { return err } @@ -87,7 +88,7 @@ func (r *scopeRepository) Find(ctx context.Context, id int, full bool) (*models. } eg.Go(func() error { - members, err := q.ListScopeMembers(ctx, id) + members, err := r.q.ListScopeMembers(ctx, id) if err != nil && err != sql.ErrNoRows { return err } @@ -112,8 +113,7 @@ func (r *scopeRepository) Find(ctx context.Context, id int, full bool) (*models. } func (r *scopeRepository) List(ctx context.Context) ([]models.ScopeEntry, error) { - q := mysqlcore.New(r.db) - scopes, err := q.ListScopes(ctx) + scopes, err := r.q.ListScopes(ctx) if err != nil { return nil, err } @@ -131,8 +131,7 @@ func (r *scopeRepository) List(ctx context.Context) ([]models.ScopeEntry, error) } func (r *scopeRepository) ListByUser(ctx context.Context, userID string) ([]models.ScopeEntry, error) { - q := mysqlcore.New(r.db) - scopes, err := q.ListScopesByUser(ctx, userID) + scopes, err := r.q.ListScopesByUser(ctx, userID) if err != nil { return nil, err } @@ -156,7 +155,7 @@ func (r *scopeRepository) Create(ctx context.Context, scope models.ScopeEntry, o } defer tx.Rollback() - q := mysqlcore.New(tx) + q := r.q.WithTx(tx) res, err := q.InsertScope(ctx, mysqlcore.InsertScopeParams(scope)) if err != nil { return nil, err @@ -212,7 +211,7 @@ func (r *scopeRepository) Delete(ctx context.Context, scope models.Scope) error } defer tx.Rollback() - q := mysqlcore.New(tx) + q := r.q.WithTx(tx) err = q.DeleteAllScopeMembers(ctx, scope.ID) if err != nil { return err diff --git a/internal/models/item.go b/internal/models/item.go index 8e422f9..aa4623b 100644 --- a/internal/models/item.go +++ b/internal/models/item.go @@ -16,7 +16,6 @@ type Item struct { ScheduledDate *Date `json:"scheduledDate"` Stats []StatProgressEntry `json:"stats,omitempty"` - Owner *ScopeMember `json:"owner,omitempty"` } func (item *Item) ApplyUpdate(update ItemUpdate) { @@ -25,7 +24,6 @@ func (item *Item) ApplyUpdate(update ItemUpdate) { } if update.OwnerID != nil { item.OwnerID = *update.OwnerID - item.Owner = nil } if update.Name != nil { item.Name = *update.Name diff --git a/internal/models/project.go b/internal/models/project.go index 63ce8b2..e057439 100644 --- a/internal/models/project.go +++ b/internal/models/project.go @@ -1,9 +1,13 @@ package models +import "time" + type ProjectEntry struct { - ID int `json:"id"` - Name string `json:"name"` - Status Status `json:"status"` + ID int `json:"id"` + OwnerID string `json:"ownerId,omitempty"` + CreatedTime time.Time `json:"createdTime"` + Name string `json:"name"` + Status Status `json:"status"` } type ProjectRequirement struct { @@ -15,6 +19,39 @@ type ProjectRequirement struct { Items []Item `json:"items,omitempty"` } +func (requirement *ProjectRequirement) Update(update ProjectRequirementUpdate) { + if update.Name != nil { + requirement.Name = *update.Name + } + if update.Description != nil { + requirement.Description = *update.Description + } + if update.Status != nil { + requirement.Status = *update.Status + } + + deleteList := make([]int, 0, len(update.Stats)) + for i, stat := range update.Stats { + if stat.Acquired == 0 && stat.Required == 0 { + deleteList = append(deleteList, i-len(deleteList)) + } + + found := false + for j := range requirement.Stats { + if requirement.Stats[j].ID == stat.ID { + requirement.Stats[j].Required = stat.Required + requirement.Stats[j].Acquired = stat.Acquired + + found = true + break + } + } + if !found { + requirement.Stats = append(requirement.Stats, stat) + } + } +} + type ProjectRequirementUpdate struct { Name *string `json:"name"` Description *string `json:"description"` @@ -24,6 +61,6 @@ type ProjectRequirementUpdate struct { type Project struct { ProjectEntry - Description string - Requirements []ProjectRequirement + Description string `json:"description"` + Requirements []ProjectRequirement `json:"requirements"` } diff --git a/internal/models/scope.go b/internal/models/scope.go index 47689dc..a7bee1a 100644 --- a/internal/models/scope.go +++ b/internal/models/scope.go @@ -15,10 +15,11 @@ type ScopeMember struct { type Scope struct { ScopeEntry - DisplayName string `json:"displayName"` - Members []ScopeMember `json:"members,omitempty"` - Projects []ProjectEntry `json:"projects,omitempty"` - Stats []Stat `json:"stats,omitempty"` + DisplayName string `json:"displayName"` + Members []ScopeMember `json:"members,omitempty"` + Projects []ProjectEntry `json:"projects,omitempty"` + Stats []Stat `json:"stats,omitempty"` + StatusLabels map[Status]string `json:"statusLabels,omitempty"` // Not stored as part of scope, but may be in the future. } func (s *Scope) Member(id string) *ScopeMember { diff --git a/internal/models/status.go b/internal/models/status.go index 066ac47..3f22dc5 100644 --- a/internal/models/status.go +++ b/internal/models/status.go @@ -2,14 +2,20 @@ package models type Status int +func (s Status) Valid() bool { + return s >= 0 && s < maxStatus +} + const ( - Blocked Status = 0 - Available Status = 1 - Background Status = 2 - Active Status = 3 - Completed Status = 4 - Failed Status = 5 - Dropped Status = 6 + Blocked Status = iota + Available + Background + Active + Completed + Failed + Dropped + + maxStatus ) var StatusLabels = map[Status]string{ diff --git a/internal/slerrors/notfound.go b/internal/slerrors/notfound.go index 9b493f8..b9d2fcc 100644 --- a/internal/slerrors/notfound.go +++ b/internal/slerrors/notfound.go @@ -5,7 +5,7 @@ type notFoundError struct { } func (err *notFoundError) Error() string { - return err.Subject + " not found" + return err.Subject + " not found or inaccessible" } func NotFound(subject string) error { diff --git a/sqlc.yaml b/sqlc.yaml index 74ca795..2154696 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -5,7 +5,7 @@ packages: queries: "./internal/database/mysql/queries" schema: "./scripts/goose-mysql" engine: "mysql" - emit_prepared_queries: false + emit_prepared_queries: true emit_interface: false emit_exact_table_names: false emit_empty_slices: true