diff --git a/api/scope.go b/api/scope.go index 7d05548..7967d3e 100644 --- a/api/scope.go +++ b/api/scope.go @@ -8,11 +8,11 @@ import ( ) func Scopes(g *gin.RouterGroup, db database.Database) { - g.GET("/", handler("goals", func(c *gin.Context) (interface{}, error) { + g.GET("/", handler("scopes", func(c *gin.Context) (interface{}, error) { return db.Scopes().ListByUser(c.Request.Context(), auth.UserID(c)) })) - g.GET("/:id", handler("goals", func(c *gin.Context) (interface{}, error) { + g.GET("/:id", handler("scope", func(c *gin.Context) (interface{}, error) { id, err := reqInt(c, "id") if err != nil { return nil, err diff --git a/internal/database/mysql/mysqlcore/db.go b/internal/database/mysql/mysqlcore/db.go index 2c638ac..60a8a62 100644 --- a/internal/database/mysql/mysqlcore/db.go +++ b/internal/database/mysql/mysqlcore/db.go @@ -24,6 +24,15 @@ func New(db DBTX) *Queries { 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) } @@ -33,6 +42,9 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { 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) } @@ -48,11 +60,32 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) { 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) @@ -68,6 +101,11 @@ func (q *Queries) Close() error { 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) @@ -93,6 +131,16 @@ func (q *Queries) Close() error { 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 } @@ -132,27 +180,39 @@ func (q *Queries) queryRow(ctx context.Context, stmt *sql.Stmt, query string, ar 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 } 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, } } diff --git a/internal/database/mysql/mysqlcore/scope.sql.go b/internal/database/mysql/mysqlcore/scope.sql.go index 7e7f354..86fa1e0 100644 --- a/internal/database/mysql/mysqlcore/scope.sql.go +++ b/internal/database/mysql/mysqlcore/scope.sql.go @@ -10,6 +10,38 @@ import ( "database/sql" ) +const deleteAllScopeMembers = `-- name: DeleteAllScopeMembers :exec +DELETE FROM scope_member WHERE scope_id=? +` + +func (q *Queries) DeleteAllScopeMembers(ctx context.Context, scopeID int) error { + _, err := q.exec(ctx, q.deleteAllScopeMembersStmt, deleteAllScopeMembers, scopeID) + return err +} + +const deleteScope = `-- name: DeleteScope :exec +DELETE FROM scope WHERE id=? +` + +func (q *Queries) DeleteScope(ctx context.Context, id int) error { + _, err := q.exec(ctx, q.deleteScopeStmt, deleteScope, id) + return err +} + +const deleteScopeMember = `-- name: DeleteScopeMember :exec +DELETE FROM scope_member WHERE scope_id=? AND user_id=? +` + +type DeleteScopeMemberParams struct { + ScopeID int `json:"scope_id"` + UserID string `json:"user_id"` +} + +func (q *Queries) DeleteScopeMember(ctx context.Context, arg DeleteScopeMemberParams) error { + _, err := q.exec(ctx, q.deleteScopeMemberStmt, deleteScopeMember, arg.ScopeID, arg.UserID) + return err +} + const getScope = `-- name: GetScope :one SELECT id, name, abbreviation FROM scope WHERE id = ? @@ -69,6 +101,21 @@ func (q *Queries) GetScopeWithDisplayName(ctx context.Context, arg GetScopeWithD return i, err } +const insertScope = `-- name: InsertScope :execresult +INSERT INTO scope (id, name, abbreviation) +VALUES (?, ?, ?) +` + +type InsertScopeParams struct { + ID int `json:"id"` + Name string `json:"name"` + Abbreviation string `json:"abbreviation"` +} + +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) +} + const listScopeMembers = `-- name: ListScopeMembers :many SELECT user_id, name, owner FROM scope_member WHERE scope_id = ? @@ -173,3 +220,40 @@ func (q *Queries) ListScopesByUser(ctx context.Context, userID string) ([]ListSc } return items, nil } + +const updateScope = `-- name: UpdateScope :exec +UPDATE scope SET name = ?, abbreviation = ? WHERE id = ? +` + +type UpdateScopeParams struct { + Name string `json:"name"` + Abbreviation string `json:"abbreviation"` + ID int `json:"id"` +} + +func (q *Queries) UpdateScope(ctx context.Context, arg UpdateScopeParams) error { + _, err := q.exec(ctx, q.updateScopeStmt, updateScope, arg.Name, arg.Abbreviation, arg.ID) + return err +} + +const updateScopeMember = `-- name: UpdateScopeMember :exec +REPLACE INTO scope_member (scope_id, user_id, name, owner) +VALUES (?, ?, ?, ?) +` + +type UpdateScopeMemberParams struct { + ScopeID int `json:"scope_id"` + UserID string `json:"user_id"` + Name string `json:"name"` + Owner bool `json:"owner"` +} + +func (q *Queries) UpdateScopeMember(ctx context.Context, arg UpdateScopeMemberParams) error { + _, err := q.exec(ctx, q.updateScopeMemberStmt, updateScopeMember, + arg.ScopeID, + arg.UserID, + arg.Name, + arg.Owner, + ) + return err +} diff --git a/internal/database/mysql/queries/scope.sql b/internal/database/mysql/queries/scope.sql index 8ac5518..5b03348 100644 --- a/internal/database/mysql/queries/scope.sql +++ b/internal/database/mysql/queries/scope.sql @@ -26,3 +26,22 @@ WHERE id = ? AND sm.user_id = ?; SELECT name FROM scope_member WHERE user_id = ? AND scope_id = ?; +-- name: InsertScope :execresult +INSERT INTO scope (id, name, abbreviation) +VALUES (?, ?, ?); + +-- name: UpdateScope :exec +UPDATE scope SET name = ?, abbreviation = ? WHERE id = ?; + +-- name: UpdateScopeMember :exec +REPLACE INTO scope_member (scope_id, user_id, name, owner) +VALUES (?, ?, ?, ?); + +-- name: DeleteScopeMember :exec +DELETE FROM scope_member WHERE scope_id=? AND user_id=?; + +-- name: DeleteAllScopeMembers :exec +DELETE FROM scope_member WHERE scope_id=?; + +-- name: DeleteScope :exec +DELETE FROM scope WHERE id=?; diff --git a/internal/database/mysql/scopes.go b/internal/database/mysql/scopes.go index 23dcde8..f80c0d8 100644 --- a/internal/database/mysql/scopes.go +++ b/internal/database/mysql/scopes.go @@ -4,12 +4,12 @@ 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" - "log" - "time" ) type scopeRepository struct { @@ -101,12 +101,10 @@ func (r *scopeRepository) Find(ctx context.Context, id int) (*models.Scope, erro return nil }) - t := time.Now() err = eg.Wait() if err != nil { return nil, err } - log.Println(time.Since(t)) return res, nil } @@ -150,26 +148,126 @@ func (r *scopeRepository) ListByUser(ctx context.Context, userID string) ([]mode } func (r *scopeRepository) Create(ctx context.Context, scope models.ScopeEntry, owner models.ScopeMember) (*models.Scope, error) { - //TODO implement me - panic("implement me") + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + defer tx.Rollback() + + q := mysqlcore.New(tx) + res, err := q.InsertScope(ctx, mysqlcore.InsertScopeParams(scope)) + if err != nil { + return nil, err + } + id, err := res.LastInsertId() + if err != nil { + return nil, err + } + + err = q.UpdateScopeMember(ctx, mysqlcore.UpdateScopeMemberParams{ + ScopeID: int(id), + UserID: owner.ID, + Name: owner.Name, + Owner: true, + }) + + err = tx.Commit() + if err != nil { + return nil, err + } + + return &models.Scope{ + ScopeEntry: models.ScopeEntry{ + ID: int(id), + Name: scope.Name, + Abbreviation: scope.Abbreviation, + }, + DisplayName: owner.Name, + Members: []models.ScopeMember{ + { + ID: owner.ID, + Name: owner.Name, + Owner: true, + }, + }, + Projects: []models.ProjectEntry{}, + Stats: []models.Stat{}, + }, nil } func (r *scopeRepository) Update(ctx context.Context, scope models.ScopeEntry) error { - //TODO implement me - panic("implement me") + return mysqlcore.New(r.db).UpdateScope(ctx, mysqlcore.UpdateScopeParams{ + Name: scope.Name, + Abbreviation: scope.Abbreviation, + ID: scope.ID, + }) } func (r *scopeRepository) Delete(ctx context.Context, scope models.Scope) error { - //TODO implement me - panic("implement me") + tx, err := r.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer tx.Rollback() + + q := mysqlcore.New(tx) + err = q.DeleteAllScopeMembers(ctx, scope.ID) + if err != nil { + return err + } + err = q.DeleteScope(ctx, scope.ID) + if err != nil { + return err + } + + return tx.Commit() } func (r *scopeRepository) UpdateMember(ctx context.Context, scope models.Scope, member models.ScopeMember) (*models.Scope, error) { - //TODO implement me - panic("implement me") + err := mysqlcore.New(r.db).UpdateScopeMember(ctx, mysqlcore.UpdateScopeMemberParams{ + ScopeID: scope.ID, + UserID: member.ID, + Name: member.Name, + Owner: member.Owner, + }) + if err != nil { + return nil, err + } + + found := false + scope.Members = append(scope.Members[:0:0], scope.Members...) + for i, member2 := range scope.Members { + if member2.ID == member.ID { + scope.Members[i] = member2 + found = true + } + } + if !found { + scope.Members = append(scope.Members, member) + sort.Slice(scope.Members, func(i, j int) bool { + return scope.Members[i].Name < scope.Members[j].Name + }) + } + + return &scope, nil } func (r *scopeRepository) DeleteMember(ctx context.Context, scope models.Scope, userID string) (*models.Scope, error) { - //TODO implement me - panic("implement me") + err := mysqlcore.New(r.db).DeleteScopeMember(ctx, mysqlcore.DeleteScopeMemberParams{ + ScopeID: scope.ID, + UserID: userID, + }) + if err != nil { + return nil, err + } + + newMembers := make([]models.ScopeMember, 0, len(scope.Members)-1) + for _, member := range scope.Members { + if member.ID != userID { + newMembers = append(newMembers, member) + } + } + scope.Members = newMembers + + return &scope, nil }