Gisle Aune
3 years ago
24 changed files with 1302 additions and 147 deletions
-
31api/common.go
-
127api/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
-
487internal/database/mysql/mysqlcore/db.go
-
67internal/database/mysql/mysqlcore/item.sql.go
-
195internal/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
-
27internal/database/mysql/scopes.go
-
2internal/models/item.go
-
41internal/models/project.go
-
1internal/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 |
-- name: ListProjectEntries :many |
||||
SELECT id, name, status FROM project |
|
||||
|
SELECT id, name, status, created_time, author_id FROM project |
||||
WHERE scope_id = ? |
WHERE scope_id = ? |
||||
ORDER BY status, created_time; |
ORDER BY status, created_time; |
||||
|
|
||||
-- name: GetProject :one |
-- name: GetProject :one |
||||
SELECT * FROM project WHERE id = ?; |
SELECT * FROM project WHERE id = ?; |
||||
|
|
||||
|
-- name: GetProjectRequirement :one |
||||
|
SELECT * FROM project_requirement WHERE id = ?; |
||||
|
|
||||
-- name: ListProjectRequirementsByProjectID :many |
-- name: ListProjectRequirementsByProjectID :many |
||||
SELECT * FROM project_requirement WHERE project_id = ?; |
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