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