Browse Source

add endpoints for listing requirements.

master
Gisle Aune 1 year ago
parent
commit
e983e68ed6
  1. 2
      cmd/stufflog3/main.go
  2. 18
      ports/httpapi/requirements.go
  3. 20
      ports/mysql/mysqlcore/db.go
  4. 144
      ports/mysql/mysqlcore/projects.sql.go
  5. 20
      ports/mysql/projects.go
  6. 87
      ports/mysql/queries/projects.sql
  7. 1
      usecases/projects/repository.go
  8. 6
      usecases/projects/result.go
  9. 42
      usecases/projects/service.go

2
cmd/stufflog3/main.go

@ -101,6 +101,7 @@ func main() {
apiV1ScopesSub := apiV1Scopes.Group("/:scope_id")
apiV1ScopesSub.Use(httpapi.ScopeMiddleware(scopesService, authService))
httpapi.Projects(apiV1ScopesSub.Group("/projects"), projectsService)
httpapi.Requirements(apiV1ScopesSub.Group("/requirements"), projectsService)
httpapi.Items(apiV1ScopesSub.Group("/items"), itemsService)
httpapi.Sprints(apiV1ScopesSub.Group("/sprints"), sprintsService)
httpapi.Stats(apiV1ScopesSub.Group("/stats"), statsService)
@ -108,6 +109,7 @@ func main() {
apiV1AllScopes.Use(httpapi.AllScopeMiddleware(scopesService, authService))
httpapi.ItemsAllScopes(apiV1AllScopes.Group("/items"), itemsService)
httpapi.SprintsAllScopes(apiV1AllScopes.Group("/sprints"), sprintsService)
httpapi.RequirementsAllScopes(apiV1AllScopes.Group("/requirements"), projectsService)
if *flagLocal {
exitSignal := make(chan os.Signal)

18
ports/httpapi/requirements.go

@ -0,0 +1,18 @@
package httpapi
import (
"git.aiterp.net/stufflog3/stufflog3/usecases/projects"
"github.com/gin-gonic/gin"
)
func RequirementsAllScopes(g *gin.RouterGroup, service *projects.Service) {
g.GET("", handler("requirements", func(c *gin.Context) (interface{}, error) {
return service.ListRequirementAllScopes(c.Request.Context())
}))
}
func Requirements(g *gin.RouterGroup, service *projects.Service) {
g.GET("", handler("requirements", func(c *gin.Context) (interface{}, error) {
return service.ListRequirements(c.Request.Context())
}))
}

20
ports/mysql/mysqlcore/db.go

@ -180,9 +180,15 @@ func Prepare(ctx context.Context, db DBTX) (*Queries, error) {
if q.listProjectRequirementsStmt, err = db.PrepareContext(ctx, listProjectRequirements); err != nil {
return nil, fmt.Errorf("error preparing query ListProjectRequirements: %w", err)
}
if q.listProjectRequirementsByScopeIDStmt, err = db.PrepareContext(ctx, listProjectRequirementsByScopeID); err != nil {
return nil, fmt.Errorf("error preparing query ListProjectRequirementsByScopeID: %w", err)
}
if q.listProjectRequirementsStatsStmt, err = db.PrepareContext(ctx, listProjectRequirementsStats); err != nil {
return nil, fmt.Errorf("error preparing query ListProjectRequirementsStats: %w", err)
}
if q.listProjectRequirementsStatsByScopeIDStmt, err = db.PrepareContext(ctx, listProjectRequirementsStatsByScopeID); err != nil {
return nil, fmt.Errorf("error preparing query ListProjectRequirementsStatsByScopeID: %w", err)
}
if q.listProjectsStmt, err = db.PrepareContext(ctx, listProjects); err != nil {
return nil, fmt.Errorf("error preparing query ListProjects: %w", err)
}
@ -508,11 +514,21 @@ func (q *Queries) Close() error {
err = fmt.Errorf("error closing listProjectRequirementsStmt: %w", cerr)
}
}
if q.listProjectRequirementsByScopeIDStmt != nil {
if cerr := q.listProjectRequirementsByScopeIDStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listProjectRequirementsByScopeIDStmt: %w", cerr)
}
}
if q.listProjectRequirementsStatsStmt != nil {
if cerr := q.listProjectRequirementsStatsStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listProjectRequirementsStatsStmt: %w", cerr)
}
}
if q.listProjectRequirementsStatsByScopeIDStmt != nil {
if cerr := q.listProjectRequirementsStatsByScopeIDStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listProjectRequirementsStatsByScopeIDStmt: %w", cerr)
}
}
if q.listProjectsStmt != nil {
if cerr := q.listProjectsStmt.Close(); cerr != nil {
err = fmt.Errorf("error closing listProjectsStmt: %w", cerr)
@ -704,7 +720,9 @@ type Queries struct {
listItemsScheduledBetweenStmt *sql.Stmt
listItemsScheduledBetweenNoScopeStmt *sql.Stmt
listProjectRequirementsStmt *sql.Stmt
listProjectRequirementsByScopeIDStmt *sql.Stmt
listProjectRequirementsStatsStmt *sql.Stmt
listProjectRequirementsStatsByScopeIDStmt *sql.Stmt
listProjectsStmt *sql.Stmt
listScopeMembersStmt *sql.Stmt
listScopeMembersMultiStmt *sql.Stmt
@ -783,7 +801,9 @@ func (q *Queries) WithTx(tx *sql.Tx) *Queries {
listItemsScheduledBetweenStmt: q.listItemsScheduledBetweenStmt,
listItemsScheduledBetweenNoScopeStmt: q.listItemsScheduledBetweenNoScopeStmt,
listProjectRequirementsStmt: q.listProjectRequirementsStmt,
listProjectRequirementsByScopeIDStmt: q.listProjectRequirementsByScopeIDStmt,
listProjectRequirementsStatsStmt: q.listProjectRequirementsStatsStmt,
listProjectRequirementsStatsByScopeIDStmt: q.listProjectRequirementsStatsByScopeIDStmt,
listProjectsStmt: q.listProjectsStmt,
listScopeMembersStmt: q.listScopeMembersStmt,
listScopeMembersMultiStmt: q.listScopeMembersMultiStmt,

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

@ -22,7 +22,9 @@ func (q *Queries) ClearItemProjectRequirement(ctx context.Context, projectRequir
}
const clearProjectRequirementStatsByStat = `-- name: ClearProjectRequirementStatsByStat :exec
DELETE FROM project_requirement_stat WHERE stat_id = ?
DELETE
FROM project_requirement_stat
WHERE stat_id = ?
`
func (q *Queries) ClearProjectRequirementStatsByStat(ctx context.Context, statID int) error {
@ -31,7 +33,9 @@ func (q *Queries) ClearProjectRequirementStatsByStat(ctx context.Context, statID
}
const deleteAllProjectRequirementStats = `-- name: DeleteAllProjectRequirementStats :exec
DELETE FROM project_requirement_stat WHERE project_requirement_id = ?
DELETE
FROM project_requirement_stat
WHERE project_requirement_id = ?
`
func (q *Queries) DeleteAllProjectRequirementStats(ctx context.Context, projectRequirementID int) error {
@ -40,7 +44,9 @@ func (q *Queries) DeleteAllProjectRequirementStats(ctx context.Context, projectR
}
const deleteAllProjectRequirements = `-- name: DeleteAllProjectRequirements :exec
DELETE FROM project_requirement WHERE project_id = ?
DELETE
FROM project_requirement
WHERE project_id = ?
`
func (q *Queries) DeleteAllProjectRequirements(ctx context.Context, projectID int) error {
@ -49,7 +55,9 @@ func (q *Queries) DeleteAllProjectRequirements(ctx context.Context, projectID in
}
const deleteAllScopeProjectRequirements = `-- name: DeleteAllScopeProjectRequirements :exec
DELETE FROM project_requirement WHERE scope_id = ?
DELETE
FROM project_requirement
WHERE scope_id = ?
`
func (q *Queries) DeleteAllScopeProjectRequirements(ctx context.Context, scopeID int) error {
@ -58,7 +66,9 @@ func (q *Queries) DeleteAllScopeProjectRequirements(ctx context.Context, scopeID
}
const deleteAllScopeProjects = `-- name: DeleteAllScopeProjects :exec
DELETE FROM project WHERE scope_id = ?
DELETE
FROM project
WHERE scope_id = ?
`
func (q *Queries) DeleteAllScopeProjects(ctx context.Context, scopeID int) error {
@ -67,7 +77,10 @@ func (q *Queries) DeleteAllScopeProjects(ctx context.Context, scopeID int) error
}
const deleteProject = `-- name: DeleteProject :exec
DELETE FROM project WHERE id = ? AND scope_id = ?
DELETE
FROM project
WHERE id = ?
AND scope_id = ?
`
type DeleteProjectParams struct {
@ -81,7 +94,10 @@ func (q *Queries) DeleteProject(ctx context.Context, arg DeleteProjectParams) er
}
const deleteProjectRequirement = `-- name: DeleteProjectRequirement :exec
DELETE FROM project_requirement WHERE id = ? AND scope_id = ?
DELETE
FROM project_requirement
WHERE id = ?
AND scope_id = ?
`
type DeleteProjectRequirementParams struct {
@ -95,7 +111,10 @@ func (q *Queries) DeleteProjectRequirement(ctx context.Context, arg DeleteProjec
}
const deleteProjectRequirementStat = `-- name: DeleteProjectRequirementStat :exec
DELETE FROM project_requirement_stat WHERE project_requirement_id = ? AND stat_id = ?
DELETE
FROM project_requirement_stat
WHERE project_requirement_id = ?
AND stat_id = ?
`
type DeleteProjectRequirementStatParams struct {
@ -109,7 +128,10 @@ func (q *Queries) DeleteProjectRequirementStat(ctx context.Context, arg DeletePr
}
const getProject = `-- name: GetProject :one
SELECT id, scope_id, owner_id, name, status, description, created_time FROM project WHERE id = ? AND scope_id = ?
SELECT id, scope_id, owner_id, name, status, description, created_time
FROM project
WHERE id = ?
AND scope_id = ?
`
type GetProjectParams struct {
@ -183,7 +205,9 @@ func (q *Queries) InsertProjectRequirement(ctx context.Context, arg InsertProjec
}
const listProjectRequirements = `-- name: ListProjectRequirements :many
SELECT id, scope_id, project_id, name, status, description, is_coarse, aggregate_required FROM project_requirement WHERE project_id = ?
SELECT id, scope_id, project_id, name, status, description, is_coarse, aggregate_required
FROM project_requirement
WHERE project_id = ?
`
func (q *Queries) ListProjectRequirements(ctx context.Context, projectID int) ([]ProjectRequirement, error) {
@ -218,9 +242,48 @@ func (q *Queries) ListProjectRequirements(ctx context.Context, projectID int) ([
return items, nil
}
const listProjectRequirementsByScopeID = `-- name: ListProjectRequirementsByScopeID :many
SELECT id, scope_id, project_id, name, status, description, is_coarse, aggregate_required
FROM project_requirement
WHERE scope_id = ?
`
func (q *Queries) ListProjectRequirementsByScopeID(ctx context.Context, scopeID int) ([]ProjectRequirement, error) {
rows, err := q.query(ctx, q.listProjectRequirementsByScopeIDStmt, listProjectRequirementsByScopeID, scopeID)
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,
&i.IsCoarse,
&i.AggregateRequired,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listProjectRequirementsStats = `-- name: ListProjectRequirementsStats :many
SELECT prs.project_requirement_id, prs.stat_id, prs.required FROM project_requirement pr
RIGHT JOIN project_requirement_stat prs ON pr.id = prs.project_requirement_id
SELECT prs.project_requirement_id, prs.stat_id, prs.required
FROM project_requirement pr
RIGHT JOIN project_requirement_stat prs ON pr.id = prs.project_requirement_id
WHERE pr.project_id = ?
`
@ -247,8 +310,41 @@ func (q *Queries) ListProjectRequirementsStats(ctx context.Context, projectID in
return items, nil
}
const listProjectRequirementsStatsByScopeID = `-- name: ListProjectRequirementsStatsByScopeID :many
SELECT prs.project_requirement_id, prs.stat_id, prs.required
FROM project_requirement pr
RIGHT JOIN project_requirement_stat prs ON pr.id = prs.project_requirement_id
WHERE pr.scope_id = ?
`
func (q *Queries) ListProjectRequirementsStatsByScopeID(ctx context.Context, scopeID int) ([]ProjectRequirementStat, error) {
rows, err := q.query(ctx, q.listProjectRequirementsStatsByScopeIDStmt, listProjectRequirementsStatsByScopeID, scopeID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []ProjectRequirementStat{}
for rows.Next() {
var i ProjectRequirementStat
if err := rows.Scan(&i.ProjectRequirementID, &i.StatID, &i.Required); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const listProjects = `-- name: ListProjects :many
SELECT id, scope_id, owner_id, name, status, description, created_time FROM project WHERE scope_id = ? ORDER BY status, name
SELECT id, scope_id, owner_id, name, status, description, created_time
FROM project
WHERE scope_id = ?
ORDER BY status, name
`
func (q *Queries) ListProjects(ctx context.Context, scopeID int) ([]Project, error) {
@ -300,11 +396,12 @@ func (q *Queries) ReplaceProjectRequirementStat(ctx context.Context, arg Replace
const updateProject = `-- name: UpdateProject :exec
UPDATE project
SET owner_id = ?,
name = ?,
status = ?,
SET owner_id = ?,
name = ?,
status = ?,
description = ?
WHERE id = ? AND scope_id = ?
WHERE id = ?
AND scope_id = ?
`
type UpdateProjectParams struct {
@ -330,13 +427,14 @@ func (q *Queries) UpdateProject(ctx context.Context, arg UpdateProjectParams) er
const updateProjectRequirement = `-- name: UpdateProjectRequirement :exec
UPDATE project_requirement
SET name = ?,
status = ?,
description = ?,
is_coarse = ?,
SET name = ?,
status = ?,
description = ?,
is_coarse = ?,
aggregate_required = ?,
project_id = ?
WHERE id = ? AND scope_id = ?
project_id = ?
WHERE id = ?
AND scope_id = ?
`
type UpdateProjectRequirementParams struct {

20
ports/mysql/projects.go

@ -422,6 +422,20 @@ func (r *projectRepository) FetchRequirements(ctx context.Context, scopeID int,
return requirements, stats, nil
}
func (r *projectRepository) ListRequirementsByScope(ctx context.Context, scopeID int) ([]entities.Requirement, []entities.RequirementStat, error) {
reqRows, err := r.q.ListProjectRequirementsByScopeID(ctx, scopeID)
if err != nil && err != sql.ErrNoRows {
return nil, nil, err
}
statsRows, err := r.q.ListProjectRequirementsStatsByScopeID(ctx, scopeID)
if err != nil && err != sql.ErrNoRows {
return nil, nil, err
}
return r.fillRequirements(ctx, reqRows, statsRows)
}
func (r *projectRepository) ListRequirements(ctx context.Context, projectID int) ([]entities.Requirement, []entities.RequirementStat, error) {
reqRows, err := r.q.ListProjectRequirements(ctx, projectID)
if err != nil && err != sql.ErrNoRows {
@ -433,6 +447,10 @@ func (r *projectRepository) ListRequirements(ctx context.Context, projectID int)
return nil, nil, err
}
return r.fillRequirements(ctx, reqRows, statsRows)
}
func (r *projectRepository) fillRequirements(ctx context.Context, reqRows []mysqlcore.ProjectRequirement, statsRows []mysqlcore.ProjectRequirementStat) ([]entities.Requirement, []entities.RequirementStat, error) {
requirements := make([]entities.Requirement, 0, len(reqRows))
ids := make([]int, 0, len(reqRows))
for _, row := range reqRows {
@ -451,7 +469,7 @@ func (r *projectRepository) ListRequirements(ctx context.Context, projectID int)
}
// Fill tags
err = fetchTags(ctx, r.db, tagObjectKindRequirement, ids, func(id int, tag string) {
err := fetchTags(ctx, r.db, tagObjectKindRequirement, ids, func(id int, tag string) {
for i := range requirements {
if id == requirements[i].ID {
requirements[i].Tags = append(requirements[i].Tags, tag)

87
ports/mysql/queries/projects.sql

@ -1,8 +1,14 @@
-- name: ListProjects :many
SELECT * FROM project WHERE scope_id = ? ORDER BY status, name;
SELECT *
FROM project
WHERE scope_id = ?
ORDER BY status, name;
-- name: GetProject :one
SELECT * FROM project WHERE id = ? AND scope_id = ?;
SELECT *
FROM project
WHERE id = ?
AND scope_id = ?;
-- name: InsertProject :execresult
INSERT INTO project (scope_id, owner_id, name, status, description)
@ -10,17 +16,28 @@ VALUES (?, ?, ?, ?, ?);
-- name: UpdateProject :exec
UPDATE project
SET owner_id = ?,
name = ?,
status = ?,
SET owner_id = ?,
name = ?,
status = ?,
description = ?
WHERE id = ? AND scope_id = ?;
WHERE id = ?
AND scope_id = ?;
-- name: DeleteProject :exec
DELETE FROM project WHERE id = ? AND scope_id = ?;
DELETE
FROM project
WHERE id = ?
AND scope_id = ?;
-- name: ListProjectRequirements :many
SELECT * FROM project_requirement WHERE project_id = ?;
SELECT *
FROM project_requirement
WHERE project_id = ?;
-- name: ListProjectRequirementsByScopeID :many
SELECT *
FROM project_requirement
WHERE scope_id = ?;
-- name: InsertProjectRequirement :execresult
INSERT INTO project_requirement (scope_id, project_id, name, status, description, is_coarse, aggregate_required)
@ -28,19 +45,25 @@ VALUES (?, ?, ?, ?, ?, ?, ?);
-- name: UpdateProjectRequirement :exec
UPDATE project_requirement
SET name = ?,
status = ?,
description = ?,
is_coarse = ?,
SET name = ?,
status = ?,
description = ?,
is_coarse = ?,
aggregate_required = ?,
project_id = ?
WHERE id = ? AND scope_id = ?;
project_id = ?
WHERE id = ?
AND scope_id = ?;
-- name: DeleteProjectRequirement :exec
DELETE FROM project_requirement WHERE id = ? AND scope_id = ?;
DELETE
FROM project_requirement
WHERE id = ?
AND scope_id = ?;
-- name: DeleteAllProjectRequirements :exec
DELETE FROM project_requirement WHERE project_id = ?;
DELETE
FROM project_requirement
WHERE project_id = ?;
-- name: ClearItemProjectRequirement :exec
UPDATE item
@ -48,25 +71,43 @@ SET project_requirement_id = NULL
WHERE project_requirement_id = ?;
-- name: ListProjectRequirementsStats :many
SELECT prs.* FROM project_requirement pr
RIGHT JOIN project_requirement_stat prs ON pr.id = prs.project_requirement_id
SELECT prs.*
FROM project_requirement pr
RIGHT JOIN project_requirement_stat prs ON pr.id = prs.project_requirement_id
WHERE pr.project_id = ?;
-- name: ListProjectRequirementsStatsByScopeID :many
SELECT prs.*
FROM project_requirement pr
RIGHT JOIN project_requirement_stat prs ON pr.id = prs.project_requirement_id
WHERE pr.scope_id = ?;
-- name: ReplaceProjectRequirementStat :exec
REPLACE INTO project_requirement_stat (project_requirement_id, stat_id, required)
VALUES (?, ?, ?);
-- name: DeleteProjectRequirementStat :exec
DELETE FROM project_requirement_stat WHERE project_requirement_id = ? AND stat_id = ?;
DELETE
FROM project_requirement_stat
WHERE project_requirement_id = ?
AND stat_id = ?;
-- name: DeleteAllProjectRequirementStats :exec
DELETE FROM project_requirement_stat WHERE project_requirement_id = ?;
DELETE
FROM project_requirement_stat
WHERE project_requirement_id = ?;
-- name: ClearProjectRequirementStatsByStat :exec
DELETE FROM project_requirement_stat WHERE stat_id = ?;
DELETE
FROM project_requirement_stat
WHERE stat_id = ?;
-- name: DeleteAllScopeProjects :exec
DELETE FROM project WHERE scope_id = ?;
DELETE
FROM project
WHERE scope_id = ?;
-- name: DeleteAllScopeProjectRequirements :exec
DELETE FROM project_requirement WHERE scope_id = ?;
DELETE
FROM project_requirement
WHERE scope_id = ?;

1
usecases/projects/repository.go

@ -15,6 +15,7 @@ type Repository interface {
Update(ctx context.Context, project entities.Project, update models.ProjectUpdate) error
Delete(ctx context.Context, project entities.Project) error
FetchRequirements(ctx context.Context, scopeID int, requirementIDs ...int) ([]entities.Requirement, []entities.RequirementStat, error)
ListRequirementsByScope(ctx context.Context, scopeID int) ([]entities.Requirement, []entities.RequirementStat, error)
ListRequirements(ctx context.Context, projectID int) ([]entities.Requirement, []entities.RequirementStat, error)
CreateRequirement(ctx context.Context, requirement entities.Requirement) (*entities.Requirement, error)
UpdateRequirement(ctx context.Context, requirement entities.Requirement, update models.RequirementUpdate) error

6
usecases/projects/result.go

@ -66,7 +66,7 @@ type RequirementResult struct {
IsCoarse bool `json:"isCoarse"`
AggregateRequired int `json:"aggregateRequired"`
Stats []RequirementResultStat `json:"stats"`
Items []items.Result `json:"items"`
Items []items.Result `json:"items,omitempty"`
Tags []string `json:"tags"`
Requirement entities.Requirement `json:"-"`
@ -283,5 +283,9 @@ func generateRequirementResult(req entities.Requirement, scope scopes.Result, re
}
}
if projectItems == nil {
resReq.Items = nil
}
return resReq
}

42
usecases/projects/service.go

@ -67,6 +67,48 @@ func (s *Service) List(ctx context.Context) ([]Entry, error) {
return entries, nil
}
func (s *Service) ListRequirementAllScopes(ctx context.Context) ([]RequirementResult, error) {
allScopes, err := s.Scopes.Context(ctx).Scopes(ctx)
if err != nil {
return nil, err
}
var res []RequirementResult
for _, scope := range allScopes {
scopedRes, err := s.listRequirements(ctx, scope)
if err != nil {
return nil, err
}
res = append(res, scopedRes...)
}
return res, nil
}
func (s *Service) ListRequirements(ctx context.Context) ([]RequirementResult, error) {
return s.listRequirements(ctx, s.Scopes.Context(ctx).Scope)
}
func (s *Service) listRequirements(ctx context.Context, sc scopes.Result) ([]RequirementResult, error) {
requirements, requirementStats, err := s.Repository.ListRequirementsByScope(ctx, sc.ID)
if err != nil {
return nil, err
}
results := make([]RequirementResult, 0, len(requirements))
for _, req := range requirements {
results = append(results, generateRequirementResult(
req,
sc,
requirementStats,
nil,
))
}
return results, nil
}
func (s *Service) ListByTags(ctx context.Context, tags []string) (interface{}, error) {
projects, err := s.Repository.ListByTags(ctx, s.Scopes.Context(ctx).ID, tags)
if err != nil {

Loading…
Cancel
Save