diff --git a/api/common.go b/api/common.go index 6d6b507..5a44c4c 100644 --- a/api/common.go +++ b/api/common.go @@ -1,8 +1,13 @@ package api import ( + "context" + "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" + "net/http" "strconv" ) @@ -29,3 +34,37 @@ func reqInt(c *gin.Context, key string) (int, error) { return v, nil } + +var scopeIdContextKey = struct{ stuff string }{stuff: "Things"} + +func scopeIDMiddleware(db database.Database) gin.HandlerFunc { + return func(c *gin.Context) { + scopeID, err := strconv.Atoi(c.Param("scope_id")) + if err != nil { + c.AbortWithStatusJSON(http.StatusUnauthorized, slerrors.ErrorResponse{ + Code: http.StatusBadRequest, + Message: "Invalid scope ID in path!", + }) + + return + } + + scope, err := db.Scopes().Find(c.Request.Context(), scopeID, c.Request.Method != "GET") + if err != nil || !scope.HasMember(auth.UserID(c)) { + c.AbortWithStatusJSON(http.StatusUnauthorized, slerrors.ErrorResponse{ + Code: http.StatusNotFound, + Message: "Scope not found or you don't have permission to it!", + }) + + return + } + + c.Request = c.Request.WithContext( + context.WithValue(c.Request.Context(), &scopeIdContextKey, scope), + ) + } +} + +func getScope(c *gin.Context) *models.Scope { + return c.Request.Context().Value(&scopeIdContextKey).(*models.Scope) +} diff --git a/api/items.go b/api/items.go new file mode 100644 index 0000000..524e65a --- /dev/null +++ b/api/items.go @@ -0,0 +1,72 @@ +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 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 + } + + item, err := db.Items(getScope(c).ID).Find(c.Request.Context(), id) + if err != nil { + return nil, err + } + for _, member := range getScope(c).Members { + if member.ID == item.OwnerID { + item.Owner = &member + } + } + + return item, nil + })) + + g.POST("/", handler("item", func(c *gin.Context) (interface{}, error) { + item := &models.Item{} + err := c.BindJSON(item) + if err != nil { + return nil, slerrors.BadRequest("Invalid JSON input: " + err.Error()) + } + + item.CreatedTime = time.Now() + + if item.Name == "" { + return nil, slerrors.BadRequest("Blank item name not allowed") + } + if item.OwnerID == "" { + item.OwnerID = auth.UserID(c) + } 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.") + } + if stat.Acquired == 0 && stat.Required == 0 { + return nil, slerrors.BadRequest("0/0 stats are not allowed when creating items.") + } + } + + item, err = db.Items(getScope(c).ID).Create(c.Request.Context(), *item) + if err != nil { + return nil, err + } + item.Owner = getScope(c).Member(item.OwnerID) + + return item, err + })) +} diff --git a/api/scope.go b/api/scope.go index 7967d3e..b966e95 100644 --- a/api/scope.go +++ b/api/scope.go @@ -18,7 +18,7 @@ func Scopes(g *gin.RouterGroup, db database.Database) { return nil, err } - scope, err := db.Scopes().Find(c.Request.Context(), id) + scope, err := db.Scopes().Find(c.Request.Context(), id, true) if err != nil { return nil, err } diff --git a/cmd/stufflog3-server.go b/cmd/stufflog3-server.go index db2a398..5a69139 100644 --- a/cmd/stufflog3-server.go +++ b/cmd/stufflog3-server.go @@ -34,6 +34,7 @@ func main() { } api.Scopes(server.Group("/api/scopes"), db) + api.Items(server.Group("/api/scope/:scope_id/items"), db) exitSignal := make(chan os.Signal) signal.Notify(exitSignal, os.Interrupt, os.Kill, syscall.SIGTERM) diff --git a/internal/database/database.go b/internal/database/database.go index fcfc8f6..7c068e2 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -10,10 +10,11 @@ type Database interface { Scopes() ScopeRepository Stats(scopeID int) StatRepository Items(scopeID int) ItemRepository + ItemsMultiScope(scopeIDs []int) ItemRepository } type ScopeRepository interface { - Find(ctx context.Context, id int) (*models.Scope, error) + Find(ctx context.Context, id int, full bool) (*models.Scope, error) List(ctx context.Context) ([]models.ScopeEntry, error) ListByUser(ctx context.Context, userID string) ([]models.ScopeEntry, error) Create(ctx context.Context, scope models.ScopeEntry, owner models.ScopeMember) (*models.Scope, error) @@ -33,7 +34,30 @@ type StatRepository interface { type ItemRepository interface { Find(ctx context.Context, id int) (*models.Item, error) - List(ctx context.Context) ([]models.Item, error) + ListCreated(ctx context.Context, from, to time.Time) ([]models.Item, error) ListAcquired(ctx context.Context, from, to time.Time) ([]models.Item, error) - Create(ctx context.Context, stat models.Stat) (*models.Item, error) + ListScheduled(ctx context.Context, from, to models.Date) ([]models.Item, error) + ListLoose(ctx context.Context, from, to time.Time) ([]models.Item, error) + Create(ctx context.Context, item models.Item) (*models.Item, error) + Update(ctx context.Context, item models.Item, update models.ItemUpdate) (*models.Item, error) + Delete(ctx context.Context, item models.Item) error +} + +type ItemMultiScopeRepository interface { + ListCreated(ctx context.Context, from, to time.Time) ([]models.Item, error) + ListAcquired(ctx context.Context, from, to time.Time) ([]models.Item, error) + ListScheduled(ctx context.Context, from, to models.Date) ([]models.Item, error) + ListLoose(ctx context.Context, from, to time.Time) ([]models.Item, error) +} + +type ProjectRepository interface { + Find(ctx context.Context, id int) (*models.Project, error) + List(ctx context.Context) ([]models.Project, 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) } diff --git a/internal/database/mysql/database.go b/internal/database/mysql/database.go index 82189b1..44d90f7 100644 --- a/internal/database/mysql/database.go +++ b/internal/database/mysql/database.go @@ -23,6 +23,10 @@ func (d *Database) Stats(scopeID int) database.StatRepository { } func (d *Database) Items(scopeID int) database.ItemRepository { + return &itemRepository{db: d.db, scopeID: scopeID} +} + +func (d *Database) ItemsMultiScope(scopeIDs []int) database.ItemRepository { //TODO implement me panic("implement me") } diff --git a/internal/database/mysql/items.go b/internal/database/mysql/items.go new file mode 100644 index 0000000..ed19914 --- /dev/null +++ b/internal/database/mysql/items.go @@ -0,0 +1,354 @@ +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" + "git.aiterp.net/stufflog3/stufflog3-api/internal/sqltypes" + "strings" + "time" +) + +type itemRepository struct { + db *sql.DB + scopeID int +} + +func (r *itemRepository) Find(ctx context.Context, id int) (*models.Item, error) { + res, err := mysqlcore.New(r.db).GetItem(ctx, id) + if err != nil { + if err == sql.ErrNoRows { + return nil, slerrors.NotFound("Item") + } + return nil, err + } + if res.ScopeID != r.scopeID { + return nil, slerrors.NotFound("Item") + } + + item := r.resToItem(mysqlcore.ListItemsAcquiredBetweenRow(res)) + + stats, _ := mysqlcore.New(r.db).ListItemStatProgress(ctx, id) + for _, stat := range stats { + item.Stats = append(item.Stats, models.StatProgressEntry{ + StatEntry: models.StatEntry{ + ID: int(stat.ID.Int32), + Name: stat.Name.String, + Weight: stat.Weight, + }, + Acquired: stat.Acquired, + Required: stat.Required, + }) + } + + return &item, nil +} + +func (r *itemRepository) ListCreated(ctx context.Context, from, to time.Time) ([]models.Item, error) { + rows, err := mysqlcore.New(r.db).ListItemsCreatedBetween(ctx, mysqlcore.ListItemsCreatedBetweenParams{ + CreatedTime: from, + CreatedTime_2: to, + ScopeID: r.scopeID, + }) + if err != nil { + return nil, err + } + + items := make([]models.Item, 0, len(rows)) + for _, row := range rows { + items = append(items, r.resToItem(mysqlcore.ListItemsAcquiredBetweenRow(row))) + } + + err = r.fillStats(ctx, items) + if err != nil { + return nil, err + } + + return items, nil +} + +func (r *itemRepository) ListAcquired(ctx context.Context, from, to time.Time) ([]models.Item, error) { + rows, err := mysqlcore.New(r.db).ListItemsAcquiredBetween(ctx, mysqlcore.ListItemsAcquiredBetweenParams{ + AcquiredTime: sql.NullTime{Valid: true, Time: from}, + AcquiredTime_2: sql.NullTime{Valid: true, Time: to}, + ScopeID: r.scopeID, + }) + if err != nil { + return nil, err + } + + items := make([]models.Item, 0, len(rows)) + for _, row := range rows { + items = append(items, r.resToItem(row)) + } + + err = r.fillStats(ctx, items) + if err != nil { + return nil, err + } + + return items, nil +} + +func (r *itemRepository) ListScheduled(ctx context.Context, from, to models.Date) ([]models.Item, error) { + rows, err := mysqlcore.New(r.db).ListItemsScheduledBetween(ctx, mysqlcore.ListItemsScheduledBetweenParams{ + ScheduledDate: sqltypes.NullDate{Valid: true, Date: from}, + ScheduledDate_2: sqltypes.NullDate{Valid: true, Date: to}, + ScopeID: r.scopeID, + }) + if err != nil { + return nil, err + } + + items := make([]models.Item, 0, len(rows)) + for _, row := range rows { + items = append(items, r.resToItem(mysqlcore.ListItemsAcquiredBetweenRow(row))) + } + + err = r.fillStats(ctx, items) + if err != nil { + return nil, err + } + + return items, nil +} + +func (r *itemRepository) ListLoose(ctx context.Context, from, to time.Time) ([]models.Item, error) { + rows, err := mysqlcore.New(r.db).ListItemsLooseBetween(ctx, mysqlcore.ListItemsLooseBetweenParams{ + CreatedTime: from, + CreatedTime_2: to, + ScopeID: r.scopeID, + }) + if err != nil { + return nil, err + } + + items := make([]models.Item, 0, len(rows)) + for _, row := range rows { + items = append(items, r.resToItem(mysqlcore.ListItemsAcquiredBetweenRow(row))) + } + + err = r.fillStats(ctx, items) + if err != nil { + return nil, err + } + + return items, nil +} + +func (r *itemRepository) Create(ctx context.Context, item models.Item) (*models.Item, error) { + item.Stats = append(item.Stats[:0:0], item.Stats...) + + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer tx.Rollback() + q := mysqlcore.New(tx) + + prID, acqTime, schDate := r.generateNullables(item) + + res, err := q.InsertItem(ctx, mysqlcore.InsertItemParams{ + ScopeID: r.scopeID, + ProjectRequirementID: prID, + Name: item.Name, + Description: item.Description, + CreatedTime: time.Now(), + CreatedUserID: item.OwnerID, + AcquiredTime: acqTime, + ScheduledDate: schDate, + }) + if err != nil { + return nil, err + } + + id, err := res.LastInsertId() + if err != nil { + return nil, err + } + + for _, stat := range item.Stats { + err = q.ReplaceItemStatProgress(ctx, mysqlcore.ReplaceItemStatProgressParams{ + ItemID: int(id), + StatID: stat.ID, + Acquired: stat.Acquired, + Required: stat.Required, + }) + if err != nil { + return nil, err + } + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + return r.Find(ctx, int(id)) +} + +func (r *itemRepository) generateNullables(item models.Item) (prID sql.NullInt32, acqTime sql.NullTime, schDate sqltypes.NullDate) { + if item.ProjectRequirementID != nil { + prID.Valid = true + prID.Int32 = int32(*item.ProjectRequirementID) + } + if item.AcquiredTime != nil { + acqTime.Valid = true + acqTime.Time = *item.AcquiredTime + } + if item.ScheduledDate != nil { + schDate.Valid = true + schDate.Date = *item.ScheduledDate + } + + return +} + +func (r *itemRepository) Update(ctx context.Context, item models.Item, update models.ItemUpdate) (*models.Item, error) { + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer tx.Rollback() + q := mysqlcore.New(r.db) + + item.ApplyUpdate(update) + + prID, acqTime, schDate := r.generateNullables(item) + + err = q.UpdateItem(ctx, mysqlcore.UpdateItemParams{ + ID: item.ID, + + ProjectRequirementID: prID, + Name: item.Name, + Description: item.Description, + AcquiredTime: acqTime, + ScheduledDate: schDate, + CreatedUserID: item.OwnerID, + }) + if err != nil { + return nil, err + } + + for _, stat := range update.Stats { + if stat.Acquired == 0 && stat.Required == 0 { + err = q.DeleteItemStatProgress(ctx, mysqlcore.DeleteItemStatProgressParams{ + ItemID: item.ID, + StatID: stat.ID, + }) + } else { + err = q.ReplaceItemStatProgress(ctx, mysqlcore.ReplaceItemStatProgressParams{ + ItemID: item.ID, + StatID: stat.ID, + Acquired: stat.Acquired, + Required: stat.Required, + }) + } + + if err != nil { + return nil, err + } + } + + err = tx.Commit() + if err != nil { + return nil, err + } + + return r.Find(ctx, item.ID) +} + +func (r *itemRepository) Delete(ctx context.Context, item models.Item) error { + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer tx.Rollback() + q := mysqlcore.New(r.db) + + err = q.DeleteItem(ctx, item.ID) + if err != nil { + return err + } + err = q.ClearItemStatProgress(ctx, item.ID) + if err != nil { + return err + } + + return tx.Commit() +} + +func (r *itemRepository) resToItem(res mysqlcore.ListItemsAcquiredBetweenRow) models.Item { + item := models.Item{ + ID: res.ID, + ScopeID: res.ScopeID, + OwnerID: res.CreatedUserID, + Name: res.Name, + Description: res.Description, + CreatedTime: res.CreatedTime.UTC(), + } + if res.ProjectRequirementID.Valid { + projectRequirementID := int(res.ProjectRequirementID.Int32) + projectID := int(res.ProjectID.Int32) + item.ProjectRequirementID = &projectRequirementID + item.ProjectID = &projectID + } + if res.ScheduledDate.Valid { + item.ScheduledDate = &res.ScheduledDate.Date + } + if res.AcquiredTime.Valid { + item.AcquiredTime = &res.AcquiredTime.Time + } + + return item +} + +func (r *itemRepository) fillStats(ctx context.Context, items []models.Item) error { + if len(items) == 0 { + return nil + } + + ids := make([]interface{}, 0, len(items)) + for _, item := range items { + ids = append(ids, item.ID) + } + query := ` + 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 (?` + strings.Repeat(",?", len(ids)-1) + `); + ` + + rows, err := r.db.QueryContext(ctx, query, ids...) + if err != nil { + return err + } + + for rows.Next() { + var itemID, required, acquired, statID int + var statName string + var statWeight float64 + + err = rows.Scan(&itemID, &required, &acquired, &statID, &statName, &statWeight) + if err != nil { + return err + } + + for i := range items { + if items[i].ID == itemID { + items[i].Stats = append(items[i].Stats, models.StatProgressEntry{ + StatEntry: models.StatEntry{ + ID: statID, + Name: statName, + Weight: statWeight, + }, + Acquired: acquired, + Required: required, + }) + } + } + } + + return nil +} diff --git a/internal/database/mysql/mysqlcore/db.go b/internal/database/mysql/mysqlcore/db.go index 60a8a62..70791b9 100644 --- a/internal/database/mysql/mysqlcore/db.go +++ b/internal/database/mysql/mysqlcore/db.go @@ -7,7 +7,6 @@ package mysqlcore import ( "context" "database/sql" - "fmt" ) type DBTX interface { @@ -21,198 +20,12 @@ 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.deleteAllScopeMembersStmt, err = db.PrepareContext(ctx, deleteAllScopeMembers); err != nil { - return nil, fmt.Errorf("error preparing query DeleteAllScopeMembers: %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.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.insertScopeStmt, err = db.PrepareContext(ctx, insertScope); err != nil { - return nil, fmt.Errorf("error preparing query InsertScope: %w", err) - } - if q.listProjectEntriesStmt, err = db.PrepareContext(ctx, listProjectEntries); err != nil { - return nil, fmt.Errorf("error preparing query ListProjectEntries: %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.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.deleteAllScopeMembersStmt != nil { - if cerr := q.deleteAllScopeMembersStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing deleteAllScopeMembersStmt: %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.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.insertScopeStmt != nil { - if cerr := q.insertScopeStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing insertScopeStmt: %w", cerr) - } - } - if q.listProjectEntriesStmt != nil { - if cerr := q.listProjectEntriesStmt.Close(); cerr != nil { - err = fmt.Errorf("error closing listProjectEntriesStmt: %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.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 - tx *sql.Tx - deleteAllScopeMembersStmt *sql.Stmt - deleteScopeStmt *sql.Stmt - deleteScopeMemberStmt *sql.Stmt - getScopeStmt *sql.Stmt - getScopeDisplayNameStmt *sql.Stmt - getScopeWithDisplayNameStmt *sql.Stmt - insertScopeStmt *sql.Stmt - listProjectEntriesStmt *sql.Stmt - listScopeMembersStmt *sql.Stmt - listScopesStmt *sql.Stmt - listScopesByUserStmt *sql.Stmt - listStatsStmt *sql.Stmt - updateScopeStmt *sql.Stmt - updateScopeMemberStmt *sql.Stmt + db DBTX } func (q *Queries) WithTx(tx *sql.Tx) *Queries { return &Queries{ - db: tx, - tx: tx, - deleteAllScopeMembersStmt: q.deleteAllScopeMembersStmt, - deleteScopeStmt: q.deleteScopeStmt, - deleteScopeMemberStmt: q.deleteScopeMemberStmt, - getScopeStmt: q.getScopeStmt, - getScopeDisplayNameStmt: q.getScopeDisplayNameStmt, - getScopeWithDisplayNameStmt: q.getScopeWithDisplayNameStmt, - insertScopeStmt: q.insertScopeStmt, - listProjectEntriesStmt: q.listProjectEntriesStmt, - listScopeMembersStmt: q.listScopeMembersStmt, - listScopesStmt: q.listScopesStmt, - listScopesByUserStmt: q.listScopesByUserStmt, - listStatsStmt: q.listStatsStmt, - updateScopeStmt: q.updateScopeStmt, - updateScopeMemberStmt: q.updateScopeMemberStmt, + db: tx, } } diff --git a/internal/database/mysql/mysqlcore/item.sql.go b/internal/database/mysql/mysqlcore/item.sql.go new file mode 100644 index 0000000..c757dae --- /dev/null +++ b/internal/database/mysql/mysqlcore/item.sql.go @@ -0,0 +1,791 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.13.0 +// source: item.sql + +package mysqlcore + +import ( + "context" + "database/sql" + "time" + + "git.aiterp.net/stufflog3/stufflog3-api/internal/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.db.ExecContext(ctx, 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.db.ExecContext(ctx, deleteItem, id) + 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.db.ExecContext(ctx, deleteItemStatProgress, arg.ItemID, arg.StatID) + return err +} + +const getItem = `-- name: GetItem :one +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.id = ? +` + +type GetItemRow struct { + ID int + ScopeID int + ProjectRequirementID sql.NullInt32 + Name string + Description string + CreatedTime time.Time + CreatedUserID string + AcquiredTime sql.NullTime + ScheduledDate sqltypes.NullDate + ProjectID sql.NullInt32 +} + +func (q *Queries) GetItem(ctx context.Context, id int) (GetItemRow, error) { + row := q.db.QueryRowContext(ctx, getItem, id) + var i GetItemRow + err := row.Scan( + &i.ID, + &i.ScopeID, + &i.ProjectRequirementID, + &i.Name, + &i.Description, + &i.CreatedTime, + &i.CreatedUserID, + &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.db.QueryRowContext(ctx, 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, created_user_id, acquired_time, scheduled_date) +VALUES (?, ?, ?, ?, ?, ?, ?, ?) +` + +type InsertItemParams struct { + ScopeID int + ProjectRequirementID sql.NullInt32 + Name string + Description string + CreatedTime time.Time + CreatedUserID string + AcquiredTime sql.NullTime + ScheduledDate sqltypes.NullDate +} + +func (q *Queries) InsertItem(ctx context.Context, arg InsertItemParams) (sql.Result, error) { + return q.db.ExecContext(ctx, insertItem, + arg.ScopeID, + arg.ProjectRequirementID, + arg.Name, + arg.Description, + arg.CreatedTime, + arg.CreatedUserID, + 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.db.QueryContext(ctx, 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.db.QueryContext(ctx, 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.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.scope_id = ? +` + +type ListItemsAcquiredBetweenParams struct { + AcquiredTime sql.NullTime + AcquiredTime_2 sql.NullTime + ScopeID int +} + +type ListItemsAcquiredBetweenRow struct { + ID int + ScopeID int + ProjectRequirementID sql.NullInt32 + Name string + Description string + CreatedTime time.Time + CreatedUserID string + AcquiredTime sql.NullTime + ScheduledDate sqltypes.NullDate + ProjectID sql.NullInt32 +} + +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) + 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.Name, + &i.Description, + &i.CreatedTime, + &i.CreatedUserID, + &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.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 pr.project_id = ? +` + +type ListItemsByProjectRow struct { + ID int + ScopeID int + ProjectRequirementID sql.NullInt32 + Name string + Description string + CreatedTime time.Time + CreatedUserID string + AcquiredTime sql.NullTime + ScheduledDate sqltypes.NullDate + ProjectID sql.NullInt32 +} + +func (q *Queries) ListItemsByProject(ctx context.Context, projectID int) ([]ListItemsByProjectRow, error) { + rows, err := q.db.QueryContext(ctx, 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.Name, + &i.Description, + &i.CreatedTime, + &i.CreatedUserID, + &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.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.scope_id = ? +` + +type ListItemsCreatedBetweenParams struct { + CreatedTime time.Time + CreatedTime_2 time.Time + ScopeID int +} + +type ListItemsCreatedBetweenRow struct { + ID int + ScopeID int + ProjectRequirementID sql.NullInt32 + Name string + Description string + CreatedTime time.Time + CreatedUserID string + AcquiredTime sql.NullTime + ScheduledDate sqltypes.NullDate + ProjectID sql.NullInt32 +} + +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) + 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.Name, + &i.Description, + &i.CreatedTime, + &i.CreatedUserID, + &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.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 < ? +` + +type ListItemsCreatedBetweenNoScopeParams struct { + CreatedTime time.Time + CreatedTime_2 time.Time +} + +type ListItemsCreatedBetweenNoScopeRow struct { + ID int + ScopeID int + ProjectRequirementID sql.NullInt32 + Name string + Description string + CreatedTime time.Time + CreatedUserID string + AcquiredTime sql.NullTime + ScheduledDate sqltypes.NullDate + ProjectID sql.NullInt32 +} + +func (q *Queries) ListItemsCreatedBetweenNoScope(ctx context.Context, arg ListItemsCreatedBetweenNoScopeParams) ([]ListItemsCreatedBetweenNoScopeRow, error) { + rows, err := q.db.QueryContext(ctx, 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.Name, + &i.Description, + &i.CreatedTime, + &i.CreatedUserID, + &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.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.scope_id = ? + AND i.scheduled_date IS NULL + AND i.acquired_time IS NULL +` + +type ListItemsLooseBetweenParams struct { + CreatedTime time.Time + CreatedTime_2 time.Time + ScopeID int +} + +type ListItemsLooseBetweenRow struct { + ID int + ScopeID int + ProjectRequirementID sql.NullInt32 + Name string + Description string + CreatedTime time.Time + CreatedUserID string + AcquiredTime sql.NullTime + ScheduledDate sqltypes.NullDate + ProjectID sql.NullInt32 +} + +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) + 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.Name, + &i.Description, + &i.CreatedTime, + &i.CreatedUserID, + &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.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.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 + Name string + Description string + CreatedTime time.Time + CreatedUserID string + AcquiredTime sql.NullTime + ScheduledDate sqltypes.NullDate + ProjectID sql.NullInt32 +} + +func (q *Queries) ListItemsLooseBetweenNoScope(ctx context.Context, arg ListItemsLooseBetweenNoScopeParams) ([]ListItemsLooseBetweenNoScopeRow, error) { + rows, err := q.db.QueryContext(ctx, 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.Name, + &i.Description, + &i.CreatedTime, + &i.CreatedUserID, + &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.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.scope_id = ? +` + +type ListItemsScheduledBetweenParams struct { + ScheduledDate sqltypes.NullDate + ScheduledDate_2 sqltypes.NullDate + ScopeID int +} + +type ListItemsScheduledBetweenRow struct { + ID int + ScopeID int + ProjectRequirementID sql.NullInt32 + Name string + Description string + CreatedTime time.Time + CreatedUserID string + AcquiredTime sql.NullTime + ScheduledDate sqltypes.NullDate + ProjectID sql.NullInt32 +} + +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) + 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.Name, + &i.Description, + &i.CreatedTime, + &i.CreatedUserID, + &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.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 < ? +` + +type ListItemsScheduledBetweenNoScopeParams struct { + ScheduledDate sqltypes.NullDate + ScheduledDate_2 sqltypes.NullDate +} + +type ListItemsScheduledBetweenNoScopeRow struct { + ID int + ScopeID int + ProjectRequirementID sql.NullInt32 + Name string + Description string + CreatedTime time.Time + CreatedUserID string + AcquiredTime sql.NullTime + ScheduledDate sqltypes.NullDate + ProjectID sql.NullInt32 +} + +func (q *Queries) ListItemsScheduledBetweenNoScope(ctx context.Context, arg ListItemsScheduledBetweenNoScopeParams) ([]ListItemsScheduledBetweenNoScopeRow, error) { + rows, err := q.db.QueryContext(ctx, 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.Name, + &i.Description, + &i.CreatedTime, + &i.CreatedUserID, + &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.db.ExecContext(ctx, 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 = ?, + created_user_id = ? +WHERE id = ? +` + +type UpdateItemParams struct { + ProjectRequirementID sql.NullInt32 + Name string + Description string + AcquiredTime sql.NullTime + ScheduledDate sqltypes.NullDate + CreatedUserID string + ID int +} + +func (q *Queries) UpdateItem(ctx context.Context, arg UpdateItemParams) error { + _, err := q.db.ExecContext(ctx, updateItem, + arg.ProjectRequirementID, + arg.Name, + arg.Description, + arg.AcquiredTime, + arg.ScheduledDate, + arg.CreatedUserID, + arg.ID, + ) + return err +} diff --git a/internal/database/mysql/mysqlcore/models.go b/internal/database/mysql/mysqlcore/models.go index 8cf502f..cd41771 100644 --- a/internal/database/mysql/mysqlcore/models.go +++ b/internal/database/mysql/mysqlcore/models.go @@ -12,67 +12,67 @@ import ( ) type Item struct { - ID int `json:"id"` - ScopeID int `json:"scope_id"` - ProjectRequirementID sql.NullInt32 `json:"project_requirement_id"` - Name string `json:"name"` - Description string `json:"description"` - CreatedTime time.Time `json:"created_time"` - CreatedUserID string `json:"created_user_id"` - AcquiredTime sql.NullTime `json:"acquired_time"` - ScheduledDate sql.NullTime `json:"scheduled_date"` + ID int + ScopeID int + ProjectRequirementID sql.NullInt32 + Name string + Description string + CreatedTime time.Time + CreatedUserID string + AcquiredTime sql.NullTime + ScheduledDate sqltypes.NullDate } type ItemStatProgress struct { - ItemID int `json:"item_id"` - StatID int `json:"stat_id"` - Acquired int `json:"acquired"` - Required int `json:"required"` + ItemID int + StatID int + Acquired int + Required int } type Project struct { - ID int `json:"id"` - ScopeID int `json:"scope_id"` - AuthorID string `json:"author_id"` - Name string `json:"name"` - Status int `json:"status"` - Description string `json:"description"` - CreatedTime time.Time `json:"created_time"` + ID int + ScopeID int + AuthorID string + Name string + Status int + Description string + CreatedTime time.Time } type ProjectRequirement struct { - ID int `json:"id"` - ScopeID int `json:"scope_id"` - ProjectID int `json:"project_id"` - Name string `json:"name"` - Status int `json:"status"` - Description string `json:"description"` + ID int + ScopeID int + ProjectID int + Name string + Status int + Description string } type ProjectRequirementStat struct { - ProjectRequirementID int `json:"project_requirement_id"` - StatID int `json:"stat_id"` - Required int `json:"required"` + ProjectRequirementID int + StatID int + Required int } type Scope struct { - ID int `json:"id"` - Name string `json:"name"` - Abbreviation string `json:"abbreviation"` + ID int + Name string + Abbreviation string } type ScopeMember struct { - ScopeID int `json:"scope_id"` - UserID string `json:"user_id"` - Name string `json:"name"` - Owner bool `json:"owner"` + ScopeID int + UserID string + Name string + Owner bool } type Stat struct { - ID int `json:"id"` - ScopeID int `json:"scope_id"` - Name string `json:"name"` - Description string `json:"description"` - Weight float64 `json:"weight"` - AllowedAmounts sqltypes.NullRawMessage `json:"allowed_amounts"` + ID int + ScopeID int + Name string + Description string + Weight float64 + AllowedAmounts sqltypes.NullRawMessage } diff --git a/internal/database/mysql/mysqlcore/project.sql.go b/internal/database/mysql/mysqlcore/project.sql.go index 6b72a54..335be18 100644 --- a/internal/database/mysql/mysqlcore/project.sql.go +++ b/internal/database/mysql/mysqlcore/project.sql.go @@ -9,6 +9,25 @@ import ( "context" ) +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) + var i Project + err := row.Scan( + &i.ID, + &i.ScopeID, + &i.AuthorID, + &i.Name, + &i.Status, + &i.Description, + &i.CreatedTime, + ) + return i, err +} + const listProjectEntries = `-- name: ListProjectEntries :many SELECT id, name, status FROM project WHERE scope_id = ? @@ -16,13 +35,13 @@ ORDER BY status, created_time ` type ListProjectEntriesRow struct { - ID int `json:"id"` - Name string `json:"name"` - Status int `json:"status"` + ID int + Name string + Status int } func (q *Queries) ListProjectEntries(ctx context.Context, scopeID int) ([]ListProjectEntriesRow, error) { - rows, err := q.query(ctx, q.listProjectEntriesStmt, listProjectEntries, scopeID) + rows, err := q.db.QueryContext(ctx, listProjectEntries, scopeID) if err != nil { return nil, err } @@ -43,3 +62,37 @@ func (q *Queries) ListProjectEntries(ctx context.Context, scopeID int) ([]ListPr } return items, nil } + +const listProjectRequirementsByProjectID = `-- name: ListProjectRequirementsByProjectID :many +SELECT id, scope_id, project_id, name, status, description FROM project_requirement WHERE project_id = ? +` + +func (q *Queries) ListProjectRequirementsByProjectID(ctx context.Context, projectID int) ([]ProjectRequirement, error) { + rows, err := q.db.QueryContext(ctx, listProjectRequirementsByProjectID, 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 +} diff --git a/internal/database/mysql/mysqlcore/scope.sql.go b/internal/database/mysql/mysqlcore/scope.sql.go index 86fa1e0..03b209d 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.exec(ctx, q.deleteAllScopeMembersStmt, deleteAllScopeMembers, scopeID) + _, err := q.db.ExecContext(ctx, 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.exec(ctx, q.deleteScopeStmt, deleteScope, id) + _, err := q.db.ExecContext(ctx, deleteScope, id) return err } @@ -33,12 +33,12 @@ DELETE FROM scope_member WHERE scope_id=? AND user_id=? ` type DeleteScopeMemberParams struct { - ScopeID int `json:"scope_id"` - UserID string `json:"user_id"` + 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) + _, err := q.db.ExecContext(ctx, 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.queryRow(ctx, q.getScopeStmt, getScope, id) + row := q.db.QueryRowContext(ctx, getScope, id) var i Scope err := row.Scan(&i.ID, &i.Name, &i.Abbreviation) return i, err @@ -60,12 +60,12 @@ WHERE user_id = ? AND scope_id = ? ` type GetScopeDisplayNameParams struct { - UserID string `json:"user_id"` - ScopeID int `json:"scope_id"` + UserID string + ScopeID int } func (q *Queries) GetScopeDisplayName(ctx context.Context, arg GetScopeDisplayNameParams) (string, error) { - row := q.queryRow(ctx, q.getScopeDisplayNameStmt, getScopeDisplayName, arg.UserID, arg.ScopeID) + row := q.db.QueryRowContext(ctx, getScopeDisplayName, arg.UserID, arg.ScopeID) var name string err := row.Scan(&name) return name, err @@ -78,19 +78,19 @@ WHERE id = ? AND sm.user_id = ? ` type GetScopeWithDisplayNameParams struct { - ID int `json:"id"` - UserID string `json:"user_id"` + ID int + UserID string } type GetScopeWithDisplayNameRow struct { - ID int `json:"id"` - Name string `json:"name"` - Abbreviation string `json:"abbreviation"` - DisplayName sql.NullString `json:"display_name"` + ID int + Name string + Abbreviation string + DisplayName sql.NullString } func (q *Queries) GetScopeWithDisplayName(ctx context.Context, arg GetScopeWithDisplayNameParams) (GetScopeWithDisplayNameRow, error) { - row := q.queryRow(ctx, q.getScopeWithDisplayNameStmt, getScopeWithDisplayName, arg.ID, arg.UserID) + row := q.db.QueryRowContext(ctx, getScopeWithDisplayName, arg.ID, arg.UserID) var i GetScopeWithDisplayNameRow err := row.Scan( &i.ID, @@ -107,13 +107,13 @@ VALUES (?, ?, ?) ` type InsertScopeParams struct { - ID int `json:"id"` - Name string `json:"name"` - Abbreviation string `json:"abbreviation"` + ID int + Name string + Abbreviation string } 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) + return q.db.ExecContext(ctx, insertScope, arg.ID, arg.Name, arg.Abbreviation) } const listScopeMembers = `-- name: ListScopeMembers :many @@ -123,13 +123,13 @@ ORDER BY name ` type ListScopeMembersRow struct { - UserID string `json:"user_id"` - Name string `json:"name"` - Owner bool `json:"owner"` + UserID string + Name string + Owner bool } func (q *Queries) ListScopeMembers(ctx context.Context, scopeID int) ([]ListScopeMembersRow, error) { - rows, err := q.query(ctx, q.listScopeMembersStmt, listScopeMembers, scopeID) + rows, err := q.db.QueryContext(ctx, 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.query(ctx, q.listScopesStmt, listScopes) + rows, err := q.db.QueryContext(ctx, listScopes) if err != nil { return nil, err } @@ -187,14 +187,14 @@ ORDER BY s.name ` type ListScopesByUserRow struct { - ID int `json:"id"` - Name string `json:"name"` - Abbreviation string `json:"abbreviation"` - DisplayName sql.NullString `json:"display_name"` + ID int + Name string + Abbreviation string + DisplayName sql.NullString } func (q *Queries) ListScopesByUser(ctx context.Context, userID string) ([]ListScopesByUserRow, error) { - rows, err := q.query(ctx, q.listScopesByUserStmt, listScopesByUser, userID) + rows, err := q.db.QueryContext(ctx, listScopesByUser, userID) if err != nil { return nil, err } @@ -226,13 +226,13 @@ UPDATE scope SET name = ?, abbreviation = ? WHERE id = ? ` type UpdateScopeParams struct { - Name string `json:"name"` - Abbreviation string `json:"abbreviation"` - ID int `json:"id"` + Name string + Abbreviation string + ID int } func (q *Queries) UpdateScope(ctx context.Context, arg UpdateScopeParams) error { - _, err := q.exec(ctx, q.updateScopeStmt, updateScope, arg.Name, arg.Abbreviation, arg.ID) + _, err := q.db.ExecContext(ctx, updateScope, arg.Name, arg.Abbreviation, arg.ID) return err } @@ -242,14 +242,14 @@ VALUES (?, ?, ?, ?) ` type UpdateScopeMemberParams struct { - ScopeID int `json:"scope_id"` - UserID string `json:"user_id"` - Name string `json:"name"` - Owner bool `json:"owner"` + ScopeID int + UserID string + Name string + Owner bool } func (q *Queries) UpdateScopeMember(ctx context.Context, arg UpdateScopeMemberParams) error { - _, err := q.exec(ctx, q.updateScopeMemberStmt, updateScopeMember, + _, err := q.db.ExecContext(ctx, 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 4ad28bd..4d414c8 100644 --- a/internal/database/mysql/mysqlcore/stats.sql.go +++ b/internal/database/mysql/mysqlcore/stats.sql.go @@ -17,15 +17,15 @@ WHERE scope_id = ? ` type ListStatsRow struct { - ID int `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Weight float64 `json:"weight"` - AllowedAmounts sqltypes.NullRawMessage `json:"allowed_amounts"` + ID int + Name string + Description string + Weight float64 + AllowedAmounts sqltypes.NullRawMessage } func (q *Queries) ListStats(ctx context.Context, scopeID int) ([]ListStatsRow, error) { - rows, err := q.query(ctx, q.listStatsStmt, listStats, scopeID) + rows, err := q.db.QueryContext(ctx, listStats, scopeID) if err != nil { return nil, err } diff --git a/internal/database/mysql/queries/item.sql b/internal/database/mysql/queries/item.sql new file mode 100644 index 0000000..d52a2f4 --- /dev/null +++ b/internal/database/mysql/queries/item.sql @@ -0,0 +1,105 @@ +-- 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 = ?; + +-- 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 = ?; + +-- 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 = ?; + +-- 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; + +-- 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, created_user_id, acquired_time, scheduled_date) +VALUES (?, ?, ?, ?, ?, ?, ?, ?); + +-- name: UpdateItem :exec +UPDATE item +SET project_requirement_id = ?, + name = ?, + description = ?, + acquired_time = ?, + scheduled_date = ?, + created_user_id = ? +WHERE id = ?; + +-- name: DeleteItem :exec +DELETE FROM item WHERE 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/internal/database/mysql/queries/project.sql b/internal/database/mysql/queries/project.sql index ab1f410..252298a 100644 --- a/internal/database/mysql/queries/project.sql +++ b/internal/database/mysql/queries/project.sql @@ -3,3 +3,9 @@ SELECT id, name, status FROM project WHERE scope_id = ? ORDER BY status, created_time; +-- name: GetProject :one +SELECT * FROM project WHERE id = ?; + +-- name: ListProjectRequirementsByProjectID :many +SELECT * FROM project_requirement WHERE project_id = ?; + diff --git a/internal/database/mysql/scopes.go b/internal/database/mysql/scopes.go index f80c0d8..c16961e 100644 --- a/internal/database/mysql/scopes.go +++ b/internal/database/mysql/scopes.go @@ -16,7 +16,7 @@ type scopeRepository struct { db *sql.DB } -func (r *scopeRepository) Find(ctx context.Context, id int) (*models.Scope, error) { +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) if err != nil { @@ -37,64 +37,66 @@ func (r *scopeRepository) Find(ctx context.Context, id int) (*models.Scope, erro eg, ctx := errgroup.WithContext(ctx) - eg.Go(func() error { - members, err := q.ListScopeMembers(ctx, id) - if err != nil && err != sql.ErrNoRows { - return err - } + if full { + eg.Go(func() error { + projects, err := q.ListProjectEntries(ctx, id) + if err != nil && err != sql.ErrNoRows { + return err + } - for _, member := range members { - res.Members = append(res.Members, models.ScopeMember{ - ID: member.UserID, - Name: member.Name, - Owner: member.Owner, - }) - } + for _, project := range projects { + res.Projects = append(res.Projects, models.ProjectEntry{ + ID: project.ID, + Name: project.Name, + Status: models.Status(project.Status), + }) + } - return nil - }) + return nil + }) - eg.Go(func() error { - projects, err := q.ListProjectEntries(ctx, id) - if err != nil && err != sql.ErrNoRows { - return err - } + eg.Go(func() error { + stats, err := q.ListStats(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), - }) - } + for _, stat := range stats { + var amounts map[string]int + if stat.AllowedAmounts.Valid { + amounts = make(map[string]int) + err := json.Unmarshal(stat.AllowedAmounts.RawMessage, &amounts) + if err != nil { + return err + } + } - return nil - }) + res.Stats = append(res.Stats, models.Stat{ + StatEntry: models.StatEntry{ + ID: stat.ID, + Name: stat.Name, + Weight: stat.Weight, + }, + Description: stat.Description, + AllowedAmounts: amounts, + }) + } + + return nil + }) + } eg.Go(func() error { - stats, err := q.ListStats(ctx, id) + members, err := q.ListScopeMembers(ctx, id) if err != nil && err != sql.ErrNoRows { return err } - for _, stat := range stats { - var amounts map[string]int - if stat.AllowedAmounts.Valid { - amounts = make(map[string]int) - err := json.Unmarshal(stat.AllowedAmounts.RawMessage, &amounts) - if err != nil { - return err - } - } - - res.Stats = append(res.Stats, models.Stat{ - StatEntry: models.StatEntry{ - ID: stat.ID, - Name: stat.Name, - Weight: stat.Weight, - }, - Description: stat.Description, - AllowedAmounts: amounts, + for _, member := range members { + res.Members = append(res.Members, models.ScopeMember{ + ID: member.UserID, + Name: member.Name, + Owner: member.Owner, }) } diff --git a/internal/models/common.go b/internal/models/common.go new file mode 100644 index 0000000..ddfdde0 --- /dev/null +++ b/internal/models/common.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/internal/models/item.go b/internal/models/item.go index c4b0bd7..8e422f9 100644 --- a/internal/models/item.go +++ b/internal/models/item.go @@ -3,15 +3,85 @@ package models import "time" type Item struct { - ID int - ProjectRequirementID *int - OwnerID string - - Name string - Description string - OwnerName string - CreatedTime time.Time - AcquiredTime *time.Time - ScheduledDate *time.Time - Stats []StatProgressEntry + ID int `json:"id"` + ScopeID int `json:"scopeId"` + OwnerID string `json:"-"` + 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 *Date `json:"scheduledDate"` + + Stats []StatProgressEntry `json:"stats,omitempty"` + Owner *ScopeMember `json:"owner,omitempty"` +} + +func (item *Item) ApplyUpdate(update ItemUpdate) { + if update.ProjectRequirementID != nil { + item.ProjectRequirementID = update.ProjectRequirementID + } + if update.OwnerID != nil { + item.OwnerID = *update.OwnerID + item.Owner = nil + } + 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.ClearProjectRequirementID { + item.ProjectRequirementID = nil + } + if update.ClearAcquiredTime { + item.AcquiredTime = nil + } + + 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 item.Stats { + if item.Stats[j].ID == stat.ID { + item.Stats[j].Required = stat.Required + item.Stats[j].Acquired = stat.Acquired + + found = true + break + } + } + if !found { + item.Stats = append(item.Stats, stat) + } + } +} + +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"` + + Stats []StatProgressEntry `json:"stats"` + + ClearProjectRequirementID bool `json:"clearProjectRequirementId"` + ClearAcquiredTime bool `json:"clearAcquiredTime"` + ClearScheduledDate bool `json:"clearScheduledDate"` } diff --git a/internal/models/project.go b/internal/models/project.go index 0fcb12b..63ce8b2 100644 --- a/internal/models/project.go +++ b/internal/models/project.go @@ -11,5 +11,19 @@ type ProjectRequirement struct { Name string `json:"name"` Description string `json:"description"` Status Status `json:"status"` + Stats []StatProgressEntry `json:"stats,omitempty"` + Items []Item `json:"items,omitempty"` +} + +type ProjectRequirementUpdate struct { + Name *string `json:"name"` + Description *string `json:"description"` + Status *Status `json:"status"` Stats []StatProgressEntry `json:"stats"` } + +type Project struct { + ProjectEntry + Description string + Requirements []ProjectRequirement +} diff --git a/internal/models/scope.go b/internal/models/scope.go index 7b7ec9e..47689dc 100644 --- a/internal/models/scope.go +++ b/internal/models/scope.go @@ -16,9 +16,19 @@ type Scope struct { ScopeEntry DisplayName string `json:"displayName"` - Members []ScopeMember `json:"members"` - Projects []ProjectEntry `json:"projects"` - Stats []Stat `json:"stats"` + Members []ScopeMember `json:"members,omitempty"` + Projects []ProjectEntry `json:"projects,omitempty"` + Stats []Stat `json:"stats,omitempty"` +} + +func (s *Scope) Member(id string) *ScopeMember { + for _, user := range s.Members { + if user.ID == id { + return &user + } + } + + return nil } func (s *Scope) HasMember(id string) bool { @@ -30,3 +40,13 @@ func (s *Scope) HasMember(id string) bool { return false } + +func (s *Scope) Stat(id int) *Stat { + for _, stat := range s.Stats { + if stat.ID == id { + return &stat + } + } + + return nil +} diff --git a/internal/models/stat.go b/internal/models/stat.go index 8e04ef5..2cecee7 100644 --- a/internal/models/stat.go +++ b/internal/models/stat.go @@ -19,3 +19,20 @@ type Stat struct { Description string `json:"description"` AllowedAmounts map[string]int `json:"allowedAmounts"` } + +func (stat *Stat) AllowsAmount(amount int) bool { + if stat == nil { + return false + } + if stat.AllowedAmounts == nil || len(stat.AllowedAmounts) == 0 { + return true + } + + for _, v := range stat.AllowedAmounts { + if v == amount { + return true + } + } + + return false +} \ No newline at end of file diff --git a/internal/sqltypes/nulldate.go b/internal/sqltypes/nulldate.go new file mode 100644 index 0000000..eb0965c --- /dev/null +++ b/internal/sqltypes/nulldate.go @@ -0,0 +1,46 @@ +package sqltypes + +import ( + "database/sql/driver" + "errors" + "git.aiterp.net/stufflog3/stufflog3-api/internal/models" + "time" +) + +type NullDate struct { + Date models.Date + Valid bool +} + +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/sqlc.yaml b/sqlc.yaml index 4a9a41d..74ca795 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -5,17 +5,23 @@ packages: queries: "./internal/database/mysql/queries" schema: "./scripts/goose-mysql" engine: "mysql" - emit_prepared_queries: true + emit_prepared_queries: false emit_interface: false emit_exact_table_names: false emit_empty_slices: true - emit_json_tags: true + emit_json_tags: false overrides: - go_type: "git.aiterp.net/stufflog3/stufflog3-api/internal/sqltypes.NullRawMessage" db_type: "json" nullable: true + - go_type: "git.aiterp.net/stufflog3/stufflog3-api/internal/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"