Gisle Aune
3 years ago
24 changed files with 1302 additions and 147 deletions
-
31api/common.go
-
129api/items.go
-
50api/projects.go
-
2api/scope.go
-
1cmd/stufflog3-server.go
-
22go.mod
-
7internal/database/database.go
-
23internal/database/mysql/database.go
-
38internal/database/mysql/items.go
-
491internal/database/mysql/mysqlcore/db.go
-
67internal/database/mysql/mysqlcore/item.sql.go
-
201internal/database/mysql/mysqlcore/project.sql.go
-
24internal/database/mysql/mysqlcore/scope.sql.go
-
2internal/database/mysql/mysqlcore/stats.sql.go
-
180internal/database/mysql/project.go
-
27internal/database/mysql/queries/item.sql
-
39internal/database/mysql/queries/project.sql
-
33internal/database/mysql/scopes.go
-
2internal/models/item.go
-
47internal/models/project.go
-
9internal/models/scope.go
-
20internal/models/status.go
-
2internal/slerrors/notfound.go
-
2sqlc.yaml
@ -0,0 +1,50 @@ |
|||
package api |
|||
|
|||
import ( |
|||
"git.aiterp.net/stufflog3/stufflog3-api/internal/auth" |
|||
"git.aiterp.net/stufflog3/stufflog3-api/internal/database" |
|||
"git.aiterp.net/stufflog3/stufflog3-api/internal/models" |
|||
"git.aiterp.net/stufflog3/stufflog3-api/internal/slerrors" |
|||
"github.com/gin-gonic/gin" |
|||
"time" |
|||
) |
|||
|
|||
func Projects(g *gin.RouterGroup, db database.Database) { |
|||
g.Use(scopeIDMiddleware(db)) |
|||
|
|||
g.GET("/", handler("projects", func(c *gin.Context) (interface{}, error) { |
|||
return db.Projects(getScope(c).ID).List(c.Request.Context()) |
|||
})) |
|||
|
|||
g.GET("/:id", handler("project", func(c *gin.Context) (interface{}, error) { |
|||
id, err := reqInt(c, "id") |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
return db.Projects(getScope(c).ID).Find(c.Request.Context(), id) |
|||
})) |
|||
|
|||
g.POST("/", handler("project", func(c *gin.Context) (interface{}, error) { |
|||
project := &models.Project{} |
|||
err := c.BindJSON(project) |
|||
if err != nil { |
|||
return nil, slerrors.BadRequest("Invalid JSON input: " + err.Error()) |
|||
} |
|||
|
|||
if !project.Status.Valid() { |
|||
return nil, slerrors.BadRequest("Unknown/unsupported project status") |
|||
} |
|||
if project.Name == "" { |
|||
return nil, slerrors.BadRequest("Project name cannot be blank") |
|||
} |
|||
if len(project.Requirements) > 0 { |
|||
return nil, slerrors.BadRequest("You cannot submit requirements this way") |
|||
} |
|||
|
|||
project.OwnerID = auth.UserID(c) |
|||
project.CreatedTime = time.Now() |
|||
|
|||
return db.Projects(getScope(c).ID).Create(c.Request.Context(), *project) |
|||
})) |
|||
} |
@ -0,0 +1,180 @@ |
|||
package mysql |
|||
|
|||
import ( |
|||
"context" |
|||
"database/sql" |
|||
"git.aiterp.net/stufflog3/stufflog3-api/internal/database/mysql/mysqlcore" |
|||
"git.aiterp.net/stufflog3/stufflog3-api/internal/models" |
|||
"git.aiterp.net/stufflog3/stufflog3-api/internal/slerrors" |
|||
"golang.org/x/sync/errgroup" |
|||
) |
|||
|
|||
type projectRepository struct { |
|||
db *sql.DB |
|||
q *mysqlcore.Queries |
|||
items *itemRepository |
|||
scopeID int |
|||
} |
|||
|
|||
func (r *projectRepository) Find(ctx context.Context, id int) (*models.Project, error) { |
|||
row, err := r.q.GetProject(ctx, id) |
|||
if err == sql.ErrNoRows || row.ScopeID != r.scopeID { |
|||
return nil, slerrors.NotFound("Project") |
|||
} else if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
project := models.Project{ |
|||
ProjectEntry: models.ProjectEntry{ |
|||
ID: row.ID, |
|||
OwnerID: row.AuthorID, |
|||
CreatedTime: row.CreatedTime, |
|||
Name: row.Name, |
|||
Status: models.Status(row.Status), |
|||
}, |
|||
Description: row.Description, |
|||
Requirements: []models.ProjectRequirement{}, |
|||
} |
|||
|
|||
reqs, err := r.q.ListProjectRequirementsByProjectID(ctx, id) |
|||
if err != nil && err != sql.ErrNoRows { |
|||
return nil, err |
|||
} |
|||
itemRows, err := r.q.ListItemsByProject(ctx, id) |
|||
if err != nil && err != sql.ErrNoRows { |
|||
return nil, err |
|||
} |
|||
items := make([]models.Item, 0, len(itemRows)) |
|||
for _, itemRow := range itemRows { |
|||
item := r.items.resToItem(mysqlcore.ListItemsAcquiredBetweenRow(itemRow)) |
|||
items = append(items, item) |
|||
} |
|||
|
|||
err = r.items.fillStats(ctx, items) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
eg, ctx := errgroup.WithContext(ctx) |
|||
|
|||
for i := range reqs { |
|||
project.Requirements = append(project.Requirements, models.ProjectRequirement{ |
|||
ID: reqs[i].ID, |
|||
Name: reqs[i].Name, |
|||
Description: reqs[i].Description, |
|||
Status: models.Status(reqs[i].Status), |
|||
Stats: []models.StatProgressEntry{}, |
|||
Items: nil, |
|||
}) |
|||
requirement := &project.Requirements[len(project.Requirements)-1] |
|||
for _, item := range items { |
|||
if *item.ProjectRequirementID == requirement.ID { |
|||
requirement.Items = append(requirement.Items, item) |
|||
} |
|||
} |
|||
|
|||
eg.Go(func() error { |
|||
stats, err := r.q.ListProjectRequirementStats(ctx, requirement.ID) |
|||
if err != nil && err != sql.ErrNoRows { |
|||
return err |
|||
} |
|||
|
|||
for _, statRow := range stats { |
|||
stat := models.StatProgressEntry{ |
|||
StatEntry: models.StatEntry{ |
|||
ID: statRow.ID, |
|||
Name: statRow.Name, |
|||
Weight: statRow.Weight, |
|||
}, |
|||
Acquired: 0, |
|||
Required: int(statRow.Required.Int32), |
|||
} |
|||
|
|||
for _, item := range requirement.Items { |
|||
for _, stat2 := range item.Stats { |
|||
if stat2.ID == stat.ID { |
|||
stat.Acquired += stat2.Acquired |
|||
} |
|||
} |
|||
} |
|||
|
|||
requirement.Stats = append(requirement.Stats, stat) |
|||
} |
|||
|
|||
return nil |
|||
}) |
|||
} |
|||
|
|||
err = eg.Wait() |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
return &project, nil |
|||
} |
|||
|
|||
func (r *projectRepository) List(ctx context.Context) ([]models.ProjectEntry, error) { |
|||
rows, err := r.q.ListProjectEntries(ctx, r.scopeID) |
|||
if err != nil && err != sql.ErrNoRows { |
|||
return nil, err |
|||
} |
|||
|
|||
projects := make([]models.ProjectEntry, 0, len(rows)) |
|||
for _, row := range rows { |
|||
projects = append(projects, models.ProjectEntry{ |
|||
ID: row.ID, |
|||
OwnerID: row.AuthorID, |
|||
CreatedTime: row.CreatedTime, |
|||
Name: row.Name, |
|||
Status: models.Status(row.Status), |
|||
}) |
|||
} |
|||
|
|||
return projects, nil |
|||
} |
|||
|
|||
func (r *projectRepository) Create(ctx context.Context, project models.Project) (*models.Project, error) { |
|||
res, err := r.q.InsertProject(ctx, mysqlcore.InsertProjectParams{ |
|||
ScopeID: r.scopeID, |
|||
AuthorID: project.OwnerID, |
|||
Name: project.Name, |
|||
Status: int(project.Status), |
|||
Description: project.Description, |
|||
CreatedTime: project.CreatedTime, |
|||
}) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
id, err := res.LastInsertId() |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
return r.Find(ctx, int(id)) |
|||
} |
|||
|
|||
func (r *projectRepository) Update(ctx context.Context, project models.Project) (*models.Project, error) { |
|||
//TODO implement me
|
|||
panic("implement me") |
|||
} |
|||
|
|||
func (r *projectRepository) Delete(ctx context.Context, project models.ProjectEntry) error { |
|||
//TODO implement me
|
|||
panic("implement me") |
|||
} |
|||
|
|||
func (r *projectRepository) AddRequirement(ctx context.Context, project models.ProjectEntry, requirement models.ProjectRequirement) { |
|||
//TODO implement me
|
|||
panic("implement me") |
|||
} |
|||
|
|||
func (r *projectRepository) UpdateRequirement(ctx context.Context, project models.ProjectEntry, requirement models.ProjectRequirement, update models.ProjectRequirementUpdate) { |
|||
//TODO implement me
|
|||
panic("implement me") |
|||
} |
|||
|
|||
func (r *projectRepository) DeleteRequirement(ctx context.Context, project models.ProjectEntry, requirement models.ProjectRequirement, deleteItems bool) { |
|||
//TODO implement me
|
|||
panic("implement me") |
|||
} |
@ -1,11 +1,48 @@ |
|||
-- name: ListProjectEntries :many |
|||
SELECT id, name, status FROM project |
|||
SELECT id, name, status, created_time, author_id FROM project |
|||
WHERE scope_id = ? |
|||
ORDER BY status, created_time; |
|||
|
|||
-- name: GetProject :one |
|||
SELECT * FROM project WHERE id = ?; |
|||
|
|||
-- name: GetProjectRequirement :one |
|||
SELECT * FROM project_requirement WHERE id = ?; |
|||
|
|||
-- name: ListProjectRequirementsByProjectID :many |
|||
SELECT * FROM project_requirement WHERE project_id = ?; |
|||
|
|||
-- name: ListProjectRequirementStats :many |
|||
SELECT prs.required, s.id, s.name, s.weight FROM project_requirement_stat prs |
|||
RIGHT JOIN stat s ON s.id = prs.stat_id |
|||
WHERE project_requirement_id = ?; |
|||
|
|||
-- name: InsertProject :execresult |
|||
INSERT INTO project (scope_id, author_id, name, status, description, created_time) |
|||
VALUES (?, ?, ?, ?, ?, ?); |
|||
|
|||
-- name: UpdateProject :exec |
|||
UPDATE project |
|||
SET name = ?, |
|||
status = ?, |
|||
description = ? |
|||
WHERE id = ?; |
|||
|
|||
-- name: DeleteProject :exec |
|||
DELETE FROM project WHERE id = ?; |
|||
|
|||
-- name: InsertProjectRequirement :execresult |
|||
INSERT INTO project_requirement (scope_id, project_id, name, status, description) |
|||
VALUES (?, ?, ?, ?, ?); |
|||
|
|||
-- name: UpdateProjectRequirement :exec |
|||
UPDATE project_requirement |
|||
SET name = ?, |
|||
status = ?, |
|||
description = ? |
|||
WHERE id = ?; |
|||
|
|||
-- name: DeleteProjectRequirement :exec |
|||
DELETE FROM project_requirement WHERE id = ?; |
|||
|
|||
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue