Gisle Aune
3 years ago
14 changed files with 442 additions and 7 deletions
-
7api/project.go
-
93api/projectgroup.go
-
1cmd/stufflog2-lambda/main.go
-
1cmd/stufflog2-local/main.go
-
1database/database.go
-
4database/postgres/db.go
-
13database/postgres/project.go
-
140database/postgres/projectgroups.go
-
4internal/generate/ids.go
-
16migrations/postgres/20210418180353_create_table_project_group.sql
-
11migrations/postgres/20210717135720_add_project_column_project_group_id.sql
-
10models/project.go
-
65models/projectgroup.go
-
83services/loader.go
@ -0,0 +1,93 @@ |
|||
package api |
|||
|
|||
import ( |
|||
"github.com/gin-gonic/gin" |
|||
"github.com/gissleh/stufflog/database" |
|||
"github.com/gissleh/stufflog/internal/auth" |
|||
"github.com/gissleh/stufflog/internal/generate" |
|||
"github.com/gissleh/stufflog/internal/slerrors" |
|||
"github.com/gissleh/stufflog/models" |
|||
"github.com/gissleh/stufflog/services" |
|||
) |
|||
|
|||
func ProjectGroup(g *gin.RouterGroup, db database.Database) { |
|||
l := services.Loader{DB: db} |
|||
|
|||
g.GET("/", handler("projectGroups", func(c *gin.Context) (interface{}, error) { |
|||
return l.ListProjectGroups(c) |
|||
})) |
|||
|
|||
g.GET("/:id", handler("projectGroup", func(c *gin.Context) (interface{}, error) { |
|||
return l.FindProject(c, c.Param("id")) |
|||
})) |
|||
|
|||
g.POST("/", handler("projectGroup", func(c *gin.Context) (interface{}, error) { |
|||
group := models.ProjectGroup{} |
|||
err := c.BindJSON(&group) |
|||
if err != nil { |
|||
return nil, slerrors.BadRequest("Invalid JSON") |
|||
} |
|||
if group.Abbreviation == "" || group.Name == "" { |
|||
return nil, slerrors.BadRequest("Abbreviation and name cannot be left empty.") |
|||
} |
|||
|
|||
group.ID = generate.ProjectGroupID() |
|||
group.UserID = auth.UserID(c) |
|||
if group.CategoryNames == nil { |
|||
group.CategoryNames = make(map[string]string) |
|||
} |
|||
|
|||
err = db.ProjectGroups().Insert(c.Request.Context(), group) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
return &group, nil |
|||
})) |
|||
|
|||
g.PUT("/:id", handler("projectGroup", func(c *gin.Context) (interface{}, error) { |
|||
update := models.ProjectGroupUpdate{} |
|||
err := c.BindJSON(&update) |
|||
if err != nil { |
|||
return nil, slerrors.BadRequest("Invalid JSON") |
|||
} |
|||
if (update.Name != nil && *update.Name == "") || (update.Abbreviation != nil && *update.Abbreviation == "") { |
|||
return nil, slerrors.BadRequest("Abbreviation and name cannot be left empty.") |
|||
} |
|||
|
|||
group, err := db.ProjectGroups().Find(c, c.Param("id")) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
if group.UserID != auth.UserID(c) { |
|||
return nil, slerrors.NotFound("Project group") |
|||
} |
|||
|
|||
group.Update(update) |
|||
|
|||
err = db.ProjectGroups().Update(c.Request.Context(), *group) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
return group, nil |
|||
})) |
|||
|
|||
g.DELETE("/:id", handler("project", func(c *gin.Context) (interface{}, error) { |
|||
group, err := l.FindProjectGroup(c.Request.Context(), c.Param("id")) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
if len(group.Projects) > 0 { |
|||
return nil, slerrors.Forbidden("You cannot delete non-empty project group.") |
|||
} |
|||
|
|||
err = db.ProjectGroups().Delete(c.Request.Context(), group.ProjectGroup) |
|||
if err != nil { |
|||
return nil, err |
|||
} |
|||
|
|||
return group, nil |
|||
})) |
|||
} |
@ -0,0 +1,140 @@ |
|||
package postgres |
|||
|
|||
import ( |
|||
"context" |
|||
"database/sql" |
|||
"database/sql/driver" |
|||
"encoding/json" |
|||
"errors" |
|||
"github.com/gissleh/stufflog/internal/slerrors" |
|||
"github.com/gissleh/stufflog/models" |
|||
"github.com/jmoiron/sqlx" |
|||
) |
|||
|
|||
type projectGroupRepository struct { |
|||
db *sqlx.DB |
|||
} |
|||
|
|||
func (r *projectGroupRepository) Find(ctx context.Context, id string) (*models.ProjectGroup, error) { |
|||
res := projectGroupDBO{} |
|||
err := r.db.GetContext(ctx, &res, "SELECT * FROM project_group WHERE project_group_id=$1", id) |
|||
if err != nil { |
|||
if err == sql.ErrNoRows { |
|||
return nil, slerrors.NotFound("Project group") |
|||
} |
|||
|
|||
return nil, err |
|||
} |
|||
|
|||
return res.ToProjectGroup(), nil |
|||
} |
|||
|
|||
func (r *projectGroupRepository) List(ctx context.Context, filter models.ProjectGroupFilter) ([]*models.ProjectGroup, error) { |
|||
res := make([]*projectGroupDBO, 0, 16) |
|||
err := r.db.SelectContext(ctx, &res, "SELECT * FROM project_group WHERE user_id=$1", filter.UserID) |
|||
if err != nil { |
|||
if err == sql.ErrNoRows { |
|||
return []*models.ProjectGroup{}, nil |
|||
} |
|||
|
|||
return nil, err |
|||
} |
|||
|
|||
res2 := make([]*models.ProjectGroup, 0, len(res)) |
|||
for _, pdo := range res { |
|||
res2 = append(res2, pdo.ToProjectGroup()) |
|||
} |
|||
|
|||
return res2, nil |
|||
} |
|||
|
|||
func (r *projectGroupRepository) Insert(ctx context.Context, group models.ProjectGroup) error { |
|||
_, err := r.db.NamedExecContext(ctx, ` |
|||
INSERT INTO project_group ( |
|||
project_group_id, user_id, name, abbreviation, description, category_names |
|||
) VALUES ( |
|||
:project_group_id, :user_id, :name, :abbreviation, :description, :category_names |
|||
) |
|||
`, toProjectGroupDBO(group)) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
func (r *projectGroupRepository) Update(ctx context.Context, group models.ProjectGroup) error { |
|||
if group.CategoryNames == nil { |
|||
group.CategoryNames = make(map[string]string) |
|||
} |
|||
|
|||
_, err := r.db.NamedExecContext(ctx, ` |
|||
UPDATE project_group SET |
|||
name = :name, |
|||
abbreviation = :abbreviation, |
|||
description = :description, |
|||
category_names = :category_names |
|||
WHERE project_group_id = :project_group_id |
|||
`, toProjectGroupDBO(group)) |
|||
return err |
|||
} |
|||
|
|||
func (r *projectGroupRepository) Delete(ctx context.Context, group models.ProjectGroup) error { |
|||
_, err := r.db.ExecContext(ctx, `DELETE FROM project_group WHERE project_group_id=$1`, group.ID) |
|||
if err != nil { |
|||
return err |
|||
} |
|||
|
|||
_, err = r.db.ExecContext(ctx, "UPDATE project SET project_group_id=NULL where project_group_id=$1", group.ID) |
|||
if err != nil && err != sql.ErrNoRows { |
|||
return err |
|||
} |
|||
|
|||
return nil |
|||
} |
|||
|
|||
type projectGroupDBO struct { |
|||
ID string `db:"project_group_id"` |
|||
UserID string `db:"user_id"` |
|||
Name string `db:"name"` |
|||
Description string `db:"description"` |
|||
Abbreviation string `db:"abbreviation"` |
|||
CategoryNames jsonMap `db:"category_names"` |
|||
} |
|||
|
|||
func (dbo *projectGroupDBO) ToProjectGroup() *models.ProjectGroup { |
|||
return &models.ProjectGroup{ |
|||
ID: dbo.ID, |
|||
UserID: dbo.UserID, |
|||
Name: dbo.Name, |
|||
Description: dbo.Description, |
|||
Abbreviation: dbo.Abbreviation, |
|||
CategoryNames: dbo.CategoryNames, |
|||
} |
|||
} |
|||
|
|||
func toProjectGroupDBO(group models.ProjectGroup) projectGroupDBO { |
|||
return projectGroupDBO{ |
|||
ID: group.ID, |
|||
UserID: group.UserID, |
|||
Name: group.Name, |
|||
Description: group.Description, |
|||
Abbreviation: group.Abbreviation, |
|||
CategoryNames: group.CategoryNames, |
|||
} |
|||
} |
|||
|
|||
type jsonMap map[string]string |
|||
|
|||
func (m jsonMap) Value() (driver.Value, error) { |
|||
return json.Marshal(m) |
|||
} |
|||
|
|||
func (m *jsonMap) Scan(value interface{}) error { |
|||
b, ok := value.([]byte) |
|||
if !ok { |
|||
return errors.New("type assertion to []byte failed") |
|||
} |
|||
|
|||
return json.Unmarshal(b, m) |
|||
} |
@ -0,0 +1,16 @@ |
|||
-- +goose Up |
|||
-- +goose StatementBegin |
|||
CREATE TABLE project_group ( |
|||
project_group_id CHAR(16) NOT NULL PRIMARY KEY, |
|||
user_id CHAR(36) NOT NULL, |
|||
name TEXT NOT NULL, |
|||
abbreviation TEXT NOT NULL, |
|||
description TEXT NOT NULL, |
|||
category_names JSONB NOT NULL |
|||
); |
|||
-- +goose StatementEnd |
|||
|
|||
-- +goose Down |
|||
-- +goose StatementBegin |
|||
DROP TABLE IF EXISTS project_group; |
|||
-- +goose StatementEnd |
@ -0,0 +1,11 @@ |
|||
-- +goose Up |
|||
-- +goose StatementBegin |
|||
ALTER TABLE project |
|||
ADD COLUMN project_group_id CHAR(16) DEFAULT NULL; |
|||
-- +goose StatementEnd |
|||
|
|||
-- +goose Down |
|||
-- +goose StatementBegin |
|||
ALTER TABLE project |
|||
DROP COLUMN IF EXISTS project_group_id; |
|||
-- +goose StatementEnd |
Write
Preview
Loading…
Cancel
Save
Reference in new issue