diff --git a/cmd/web-api/docs/docs.go b/cmd/web-api/docs/docs.go index 71058df..ee7f0de 100644 --- a/cmd/web-api/docs/docs.go +++ b/cmd/web-api/docs/docs.go @@ -220,7 +220,7 @@ var doc = `{ "application/json" ], "tags": [ - "project" + "checklist" ], "summary": "List projects", "parameters": [ @@ -261,7 +261,7 @@ var doc = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/project.ProjectResponse" + "$ref": "#/definitions/checklist.ProjectResponse" } } }, @@ -291,7 +291,7 @@ var doc = `{ "OAuth2Password": [] } ], - "description": "Create inserts a new project into the system.", + "description": "Create inserts a new checklist into the system.", "consumes": [ "application/json" ], @@ -299,9 +299,9 @@ var doc = `{ "application/json" ], "tags": [ - "project" + "checklist" ], - "summary": "Create new project.", + "summary": "Create new checklist.", "parameters": [ { "description": "Project details", @@ -310,7 +310,7 @@ var doc = `{ "required": true, "schema": { "type": "object", - "$ref": "#/definitions/project.ProjectCreateRequest" + "$ref": "#/definitions/checklist.ProjectCreateRequest" } } ], @@ -318,7 +318,7 @@ var doc = `{ "201": { "description": "Created", "schema": { - "$ref": "#/definitions/project.ProjectResponse" + "$ref": "#/definitions/checklist.ProjectResponse" } }, "400": { @@ -353,7 +353,7 @@ var doc = `{ "OAuth2Password": [] } ], - "description": "Update updates the specified project in the system.", + "description": "Update updates the specified checklist in the system.", "consumes": [ "application/json" ], @@ -361,9 +361,9 @@ var doc = `{ "application/json" ], "tags": [ - "project" + "checklist" ], - "summary": "Update project by ID", + "summary": "Update checklist by ID", "parameters": [ { "description": "Update fields", @@ -372,7 +372,7 @@ var doc = `{ "required": true, "schema": { "type": "object", - "$ref": "#/definitions/project.ProjectUpdateRequest" + "$ref": "#/definitions/checklist.ProjectUpdateRequest" } } ], @@ -406,7 +406,7 @@ var doc = `{ "OAuth2Password": [] } ], - "description": "Archive soft-deletes the specified project from the system.", + "description": "Archive soft-deletes the specified checklist from the system.", "consumes": [ "application/json" ], @@ -414,9 +414,9 @@ var doc = `{ "application/json" ], "tags": [ - "project" + "checklist" ], - "summary": "Archive project by ID", + "summary": "Archive checklist by ID", "parameters": [ { "description": "Update fields", @@ -425,7 +425,7 @@ var doc = `{ "required": true, "schema": { "type": "object", - "$ref": "#/definitions/project.ProjectArchiveRequest" + "$ref": "#/definitions/checklist.ProjectArchiveRequest" } } ], @@ -459,7 +459,7 @@ var doc = `{ "OAuth2Password": [] } ], - "description": "Read returns the specified project from the system.", + "description": "Read returns the specified checklist from the system.", "consumes": [ "application/json" ], @@ -467,9 +467,9 @@ var doc = `{ "application/json" ], "tags": [ - "project" + "checklist" ], - "summary": "Get project by ID.", + "summary": "Get checklist by ID.", "parameters": [ { "type": "string", @@ -483,7 +483,7 @@ var doc = `{ "200": { "description": "OK", "schema": { - "$ref": "#/definitions/project.ProjectResponse" + "$ref": "#/definitions/checklist.ProjectResponse" } }, "400": { @@ -512,7 +512,7 @@ var doc = `{ "OAuth2Password": [] } ], - "description": "Delete removes the specified project from the system.", + "description": "Delete removes the specified checklist from the system.", "consumes": [ "application/json" ], @@ -520,9 +520,9 @@ var doc = `{ "application/json" ], "tags": [ - "project" + "checklist" ], - "summary": "Delete project by ID", + "summary": "Delete checklist by ID", "parameters": [ { "type": "string", @@ -1512,7 +1512,7 @@ var doc = `{ } } }, - "project.ProjectArchiveRequest": { + "checklist.ProjectArchiveRequest": { "type": "object", "required": [ "id" @@ -1524,7 +1524,7 @@ var doc = `{ } } }, - "project.ProjectCreateRequest": { + "checklist.ProjectCreateRequest": { "type": "object", "required": [ "account_id", @@ -1541,7 +1541,7 @@ var doc = `{ } } }, - "project.ProjectResponse": { + "checklist.ProjectResponse": { "type": "object", "required": [ "account_id", @@ -1579,7 +1579,7 @@ var doc = `{ } } }, - "project.ProjectUpdateRequest": { + "checklist.ProjectUpdateRequest": { "type": "object", "required": [ "id" diff --git a/cmd/web-api/docs/swagger.yaml b/cmd/web-api/docs/swagger.yaml index 5ee5fdf..f200200 100644 --- a/cmd/web-api/docs/swagger.yaml +++ b/cmd/web-api/docs/swagger.yaml @@ -673,7 +673,7 @@ paths: description: OK schema: items: - $ref: '#/definitions/project.ProjectResponse' + $ref: '#/definitions/checklist.ProjectResponse' type: array "400": description: Bad Request @@ -691,18 +691,18 @@ paths: - OAuth2Password: [] summary: List projects tags: - - project + - checklist patch: consumes: - application/json - description: Update updates the specified project in the system. + description: Update updates the specified checklist in the system. parameters: - description: Update fields in: body name: data required: true schema: - $ref: '#/definitions/project.ProjectUpdateRequest' + $ref: '#/definitions/checklist.ProjectUpdateRequest' type: object produces: - application/json @@ -722,20 +722,20 @@ paths: $ref: '#/definitions/weberror.ErrorResponse' security: - OAuth2Password: [] - summary: Update project by ID + summary: Update checklist by ID tags: - - project + - checklist post: consumes: - application/json - description: Create inserts a new project into the system. + description: Create inserts a new checklist into the system. parameters: - description: Project details in: body name: data required: true schema: - $ref: '#/definitions/project.ProjectCreateRequest' + $ref: '#/definitions/checklist.ProjectCreateRequest' type: object produces: - application/json @@ -743,7 +743,7 @@ paths: "201": description: Created schema: - $ref: '#/definitions/project.ProjectResponse' + $ref: '#/definitions/checklist.ProjectResponse' "400": description: Bad Request schema: @@ -762,14 +762,14 @@ paths: $ref: '#/definitions/weberror.ErrorResponse' security: - OAuth2Password: [] - summary: Create new project. + summary: Create new checklist. tags: - - project + - checklist /projects/{id}: delete: consumes: - application/json - description: Delete removes the specified project from the system. + description: Delete removes the specified checklist from the system. parameters: - description: Project ID in: path @@ -794,13 +794,13 @@ paths: $ref: '#/definitions/weberror.ErrorResponse' security: - OAuth2Password: [] - summary: Delete project by ID + summary: Delete checklist by ID tags: - - project + - checklist get: consumes: - application/json - description: Read returns the specified project from the system. + description: Read returns the specified checklist from the system. parameters: - description: Project ID in: path @@ -813,7 +813,7 @@ paths: "200": description: OK schema: - $ref: '#/definitions/project.ProjectResponse' + $ref: '#/definitions/checklist.ProjectResponse' "400": description: Bad Request schema: @@ -828,21 +828,21 @@ paths: $ref: '#/definitions/weberror.ErrorResponse' security: - OAuth2Password: [] - summary: Get project by ID. + summary: Get checklist by ID. tags: - - project + - checklist /projects/archive: patch: consumes: - application/json - description: Archive soft-deletes the specified project from the system. + description: Archive soft-deletes the specified checklist from the system. parameters: - description: Update fields in: body name: data required: true schema: - $ref: '#/definitions/project.ProjectArchiveRequest' + $ref: '#/definitions/checklist.ProjectArchiveRequest' type: object produces: - application/json @@ -862,9 +862,9 @@ paths: $ref: '#/definitions/weberror.ErrorResponse' security: - OAuth2Password: [] - summary: Archive project by ID + summary: Archive checklist by ID tags: - - project + - checklist /signup: post: consumes: diff --git a/cmd/web-api/handlers/project.go b/cmd/web-api/handlers/checklist.go similarity index 75% rename from cmd/web-api/handlers/project.go rename to cmd/web-api/handlers/checklist.go index ba892f8..81a6f1d 100644 --- a/cmd/web-api/handlers/project.go +++ b/cmd/web-api/handlers/checklist.go @@ -6,28 +6,28 @@ import ( "strconv" "strings" + "geeks-accelerator/oss/saas-starter-kit/internal/checklist" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" - "geeks-accelerator/oss/saas-starter-kit/internal/project" "github.com/pkg/errors" "gopkg.in/go-playground/validator.v9" ) -// Project represents the Project API method handler set. -type Projects struct { - Repository *project.Repository +// Checklist represents the Checklist API method handler set. +type Checklists struct { + Repository *checklist.Repository // ADD OTHER STATE LIKE THE LOGGER IF NEEDED. } // Find godoc -// TODO: Need to implement unittests on projects/find endpoint. There are none. -// @Summary List projects -// @Description Find returns the existing projects in the system. -// @Tags project +// TODO: Need to implement unittests on checklists/find endpoint. There are none. +// @Summary List checklists +// @Description Find returns the existing checklists in the system. +// @Tags checklist // @Accept json // @Produce json // @Security OAuth2Password @@ -36,18 +36,18 @@ type Projects struct { // @Param limit query integer false "Limit, example: 10" // @Param offset query integer false "Offset, example: 20" // @Param include-archived query boolean false "Included Archived, example: false" -// @Success 200 {array} project.ProjectResponse +// @Success 200 {array} checklist.ChecklistResponse // @Failure 400 {object} weberror.ErrorResponse // @Failure 403 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse -// @Router /projects [get] -func (h *Projects) Find(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +// @Router /checklists [get] +func (h *Checklists) Find(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { claims, ok := ctx.Value(auth.Key).(auth.Claims) if !ok { return errors.New("claims missing from context") } - var req project.ProjectFindRequest + var req checklist.ChecklistFindRequest // Handle where query value if set. if v := r.URL.Query().Get("where"); v != "" { @@ -113,7 +113,7 @@ func (h *Projects) Find(ctx context.Context, w http.ResponseWriter, r *http.Requ return err } - var resp []*project.ProjectResponse + var resp []*checklist.ChecklistResponse for _, m := range res { resp = append(resp, m.Response(ctx)) } @@ -122,19 +122,19 @@ func (h *Projects) Find(ctx context.Context, w http.ResponseWriter, r *http.Requ } // Read godoc -// @Summary Get project by ID. -// @Description Read returns the specified project from the system. -// @Tags project +// @Summary Get checklist by ID. +// @Description Read returns the specified checklist from the system. +// @Tags checklist // @Accept json // @Produce json // @Security OAuth2Password -// @Param id path string true "Project ID" -// @Success 200 {object} project.ProjectResponse +// @Param id path string true "Checklist ID" +// @Success 200 {object} checklist.ChecklistResponse // @Failure 400 {object} weberror.ErrorResponse // @Failure 404 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse -// @Router /projects/{id} [get] -func (h *Projects) Read(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +// @Router /checklists/{id} [get] +func (h *Checklists) Read(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { claims, ok := ctx.Value(auth.Key).(auth.Claims) if !ok { return errors.New("claims missing from context") @@ -151,14 +151,14 @@ func (h *Projects) Read(ctx context.Context, w http.ResponseWriter, r *http.Requ includeArchived = b } - res, err := h.Repository.Read(ctx, claims, project.ProjectReadRequest{ + res, err := h.Repository.Read(ctx, claims, checklist.ChecklistReadRequest{ ID: params["id"], IncludeArchived: includeArchived, }) if err != nil { cause := errors.Cause(err) switch cause { - case project.ErrNotFound: + case checklist.ErrNotFound: return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusNotFound)) default: return errors.Wrapf(err, "ID: %s", params["id"]) @@ -169,20 +169,20 @@ func (h *Projects) Read(ctx context.Context, w http.ResponseWriter, r *http.Requ } // Create godoc -// @Summary Create new project. -// @Description Create inserts a new project into the system. -// @Tags project +// @Summary Create new checklist. +// @Description Create inserts a new checklist into the system. +// @Tags checklist // @Accept json // @Produce json // @Security OAuth2Password -// @Param data body project.ProjectCreateRequest true "Project details" -// @Success 201 {object} project.ProjectResponse +// @Param data body checklist.ChecklistCreateRequest true "Checklist details" +// @Success 201 {object} checklist.ChecklistResponse // @Failure 400 {object} weberror.ErrorResponse // @Failure 403 {object} weberror.ErrorResponse // @Failure 404 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse -// @Router /projects [post] -func (h *Projects) Create(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +// @Router /checklists [post] +func (h *Checklists) Create(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { v, err := webcontext.ContextValues(ctx) if err != nil { return err @@ -193,7 +193,7 @@ func (h *Projects) Create(ctx context.Context, w http.ResponseWriter, r *http.Re return err } - var req project.ProjectCreateRequest + var req checklist.ChecklistCreateRequest if err := web.Decode(ctx, r, &req); err != nil { if _, ok := errors.Cause(err).(*weberror.Error); !ok { err = weberror.NewError(ctx, err, http.StatusBadRequest) @@ -205,14 +205,14 @@ func (h *Projects) Create(ctx context.Context, w http.ResponseWriter, r *http.Re if err != nil { cause := errors.Cause(err) switch cause { - case project.ErrForbidden: + case checklist.ErrForbidden: return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusForbidden)) default: _, ok := cause.(validator.ValidationErrors) if ok { return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusBadRequest)) } - return errors.Wrapf(err, "Project: %+v", &req) + return errors.Wrapf(err, "Checklist: %+v", &req) } } @@ -220,19 +220,19 @@ func (h *Projects) Create(ctx context.Context, w http.ResponseWriter, r *http.Re } // Read godoc -// @Summary Update project by ID -// @Description Update updates the specified project in the system. -// @Tags project +// @Summary Update checklist by ID +// @Description Update updates the specified checklist in the system. +// @Tags checklist // @Accept json // @Produce json // @Security OAuth2Password -// @Param data body project.ProjectUpdateRequest true "Update fields" +// @Param data body checklist.ChecklistUpdateRequest true "Update fields" // @Success 204 // @Failure 400 {object} weberror.ErrorResponse // @Failure 403 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse -// @Router /projects [patch] -func (h *Projects) Update(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +// @Router /checklists [patch] +func (h *Checklists) Update(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { v, err := webcontext.ContextValues(ctx) if err != nil { return err @@ -243,7 +243,7 @@ func (h *Projects) Update(ctx context.Context, w http.ResponseWriter, r *http.Re return err } - var req project.ProjectUpdateRequest + var req checklist.ChecklistUpdateRequest if err := web.Decode(ctx, r, &req); err != nil { if _, ok := errors.Cause(err).(*weberror.Error); !ok { err = weberror.NewError(ctx, err, http.StatusBadRequest) @@ -255,7 +255,7 @@ func (h *Projects) Update(ctx context.Context, w http.ResponseWriter, r *http.Re if err != nil { cause := errors.Cause(err) switch cause { - case project.ErrForbidden: + case checklist.ErrForbidden: return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusForbidden)) default: _, ok := cause.(validator.ValidationErrors) @@ -271,19 +271,19 @@ func (h *Projects) Update(ctx context.Context, w http.ResponseWriter, r *http.Re } // Read godoc -// @Summary Archive project by ID -// @Description Archive soft-deletes the specified project from the system. -// @Tags project +// @Summary Archive checklist by ID +// @Description Archive soft-deletes the specified checklist from the system. +// @Tags checklist // @Accept json // @Produce json // @Security OAuth2Password -// @Param data body project.ProjectArchiveRequest true "Update fields" +// @Param data body checklist.ChecklistArchiveRequest true "Update fields" // @Success 204 // @Failure 400 {object} weberror.ErrorResponse // @Failure 403 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse -// @Router /projects/archive [patch] -func (h *Projects) Archive(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +// @Router /checklists/archive [patch] +func (h *Checklists) Archive(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { v, err := webcontext.ContextValues(ctx) if err != nil { return err @@ -294,7 +294,7 @@ func (h *Projects) Archive(ctx context.Context, w http.ResponseWriter, r *http.R return err } - var req project.ProjectArchiveRequest + var req checklist.ChecklistArchiveRequest if err := web.Decode(ctx, r, &req); err != nil { if _, ok := errors.Cause(err).(*weberror.Error); !ok { err = weberror.NewError(ctx, err, http.StatusBadRequest) @@ -306,7 +306,7 @@ func (h *Projects) Archive(ctx context.Context, w http.ResponseWriter, r *http.R if err != nil { cause := errors.Cause(err) switch cause { - case project.ErrForbidden: + case checklist.ErrForbidden: return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusForbidden)) default: _, ok := cause.(validator.ValidationErrors) @@ -322,30 +322,30 @@ func (h *Projects) Archive(ctx context.Context, w http.ResponseWriter, r *http.R } // Delete godoc -// @Summary Delete project by ID -// @Description Delete removes the specified project from the system. -// @Tags project +// @Summary Delete checklist by ID +// @Description Delete removes the specified checklist from the system. +// @Tags checklist // @Accept json // @Produce json // @Security OAuth2Password -// @Param id path string true "Project ID" +// @Param id path string true "Checklist ID" // @Success 204 // @Failure 400 {object} weberror.ErrorResponse // @Failure 403 {object} weberror.ErrorResponse // @Failure 500 {object} weberror.ErrorResponse -// @Router /projects/{id} [delete] -func (h *Projects) Delete(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +// @Router /checklists/{id} [delete] +func (h *Checklists) Delete(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { claims, err := auth.ClaimsFromContext(ctx) if err != nil { return err } err = h.Repository.Delete(ctx, claims, - project.ProjectDeleteRequest{ID: params["id"]}) + checklist.ChecklistDeleteRequest{ID: params["id"]}) if err != nil { cause := errors.Cause(err) switch cause { - case project.ErrForbidden: + case checklist.ErrForbidden: return web.RespondJsonError(ctx, w, weberror.NewError(ctx, err, http.StatusForbidden)) default: _, ok := cause.(validator.ValidationErrors) diff --git a/cmd/web-api/handlers/example.go b/cmd/web-api/handlers/example.go index b448de5..b160231 100644 --- a/cmd/web-api/handlers/example.go +++ b/cmd/web-api/handlers/example.go @@ -4,18 +4,18 @@ import ( "context" "net/http" + "geeks-accelerator/oss/saas-starter-kit/internal/checklist" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" - "geeks-accelerator/oss/saas-starter-kit/internal/project" "github.com/pkg/errors" ) // Example represents the Example API method handler set. type Example struct { - Project *project.Repository + Checklist *checklist.Repository // ADD OTHER STATE LIKE THE LOGGER AND CONFIG HERE. } @@ -28,7 +28,7 @@ func (h *Example) ErrorResponse(ctx context.Context, w http.ResponseWriter, r *h } if qv := r.URL.Query().Get("test-validation-error"); qv != "" { - _, err := h.Project.Create(ctx, auth.Claims{}, project.ProjectCreateRequest{}, v.Now) + _, err := h.Checklist.Create(ctx, auth.Claims{}, checklist.ChecklistCreateRequest{}, v.Now) return web.RespondJsonError(ctx, w, err) } diff --git a/cmd/web-api/handlers/routes.go b/cmd/web-api/handlers/routes.go index 6088083..07ce923 100644 --- a/cmd/web-api/handlers/routes.go +++ b/cmd/web-api/handlers/routes.go @@ -7,13 +7,13 @@ import ( "geeks-accelerator/oss/saas-starter-kit/internal/account" "geeks-accelerator/oss/saas-starter-kit/internal/account/account_preference" + "geeks-accelerator/oss/saas-starter-kit/internal/checklist" "geeks-accelerator/oss/saas-starter-kit/internal/mid" saasSwagger "geeks-accelerator/oss/saas-starter-kit/internal/mid/saas-swagger" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" _ "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" - "geeks-accelerator/oss/saas-starter-kit/internal/project" "geeks-accelerator/oss/saas-starter-kit/internal/signup" "geeks-accelerator/oss/saas-starter-kit/internal/user" "geeks-accelerator/oss/saas-starter-kit/internal/user_account" @@ -36,7 +36,7 @@ type AppContext struct { AuthRepo *user_auth.Repository SignupRepo *signup.Repository InviteRepo *invite.Repository - ProjectRepo *project.Repository + ChecklistRepo *checklist.Repository Authenticator *auth.Authenticator PreAppMiddleware []web.Middleware PostAppMiddleware []web.Middleware @@ -74,7 +74,7 @@ func API(shutdown chan os.Signal, appCtx *AppContext) http.Handler { // Register example endpoints. ex := Example{ - Project: appCtx.ProjectRepo, + Checklist: appCtx.ChecklistRepo, } app.Handle("GET", "/v1/examples/error-response", ex.ErrorResponse) @@ -119,16 +119,16 @@ func API(shutdown chan os.Signal, appCtx *AppContext) http.Handler { } app.Handle("POST", "/v1/signup", s.Signup) - // Register project. - p := Projects{ - Repository: appCtx.ProjectRepo, + // Register checklist. + p := Checklists{ + Repository: appCtx.ChecklistRepo, } - app.Handle("GET", "/v1/projects", p.Find, mid.AuthenticateHeader(appCtx.Authenticator)) - app.Handle("POST", "/v1/projects", p.Create, mid.AuthenticateHeader(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) - app.Handle("GET", "/v1/projects/:id", p.Read, mid.AuthenticateHeader(appCtx.Authenticator)) - app.Handle("PATCH", "/v1/projects", p.Update, mid.AuthenticateHeader(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) - app.Handle("PATCH", "/v1/projects/archive", p.Archive, mid.AuthenticateHeader(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) - app.Handle("DELETE", "/v1/projects/:id", p.Delete, mid.AuthenticateHeader(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) + app.Handle("GET", "/v1/checklists", p.Find, mid.AuthenticateHeader(appCtx.Authenticator)) + app.Handle("POST", "/v1/checklists", p.Create, mid.AuthenticateHeader(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) + app.Handle("GET", "/v1/checklists/:id", p.Read, mid.AuthenticateHeader(appCtx.Authenticator)) + app.Handle("PATCH", "/v1/checklists", p.Update, mid.AuthenticateHeader(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) + app.Handle("PATCH", "/v1/checklists/archive", p.Archive, mid.AuthenticateHeader(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) + app.Handle("DELETE", "/v1/checklists/:id", p.Delete, mid.AuthenticateHeader(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) // Register swagger documentation. // TODO: Add authentication. Current authenticator requires an Authorization header diff --git a/cmd/web-api/main.go b/cmd/web-api/main.go index 649d86c..b9c9ede 100644 --- a/cmd/web-api/main.go +++ b/cmd/web-api/main.go @@ -22,18 +22,18 @@ import ( "geeks-accelerator/oss/saas-starter-kit/cmd/web-api/handlers" "geeks-accelerator/oss/saas-starter-kit/internal/account" "geeks-accelerator/oss/saas-starter-kit/internal/account/account_preference" + "geeks-accelerator/oss/saas-starter-kit/internal/checklist" "geeks-accelerator/oss/saas-starter-kit/internal/mid" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/flag" "geeks-accelerator/oss/saas-starter-kit/internal/platform/notify" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" - "geeks-accelerator/oss/saas-starter-kit/internal/project" - "geeks-accelerator/oss/saas-starter-kit/internal/project_route" "geeks-accelerator/oss/saas-starter-kit/internal/signup" "geeks-accelerator/oss/saas-starter-kit/internal/user" "geeks-accelerator/oss/saas-starter-kit/internal/user_account" "geeks-accelerator/oss/saas-starter-kit/internal/user_account/invite" "geeks-accelerator/oss/saas-starter-kit/internal/user_auth" + "geeks-accelerator/oss/saas-starter-kit/internal/webroute" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" @@ -437,19 +437,19 @@ func main() { // ========================================================================= // Init repositories and AppContext - projectRoute, err := project_route.New(cfg.Service.BaseUrl, cfg.Project.WebAppBaseUrl) + webRoute, err := webroute.New(cfg.Service.BaseUrl, cfg.Project.WebAppBaseUrl) if err != nil { - log.Fatalf("main : project routes : %s: %+v", cfg.Service.BaseUrl, err) + log.Fatalf("main : checklist routes : %s: %+v", cfg.Service.BaseUrl, err) } - usrRepo := user.NewRepository(masterDb, projectRoute.UserResetPassword, notifyEmail, cfg.Project.SharedSecretKey) + usrRepo := user.NewRepository(masterDb, webRoute.UserResetPassword, notifyEmail, cfg.Project.SharedSecretKey) usrAccRepo := user_account.NewRepository(masterDb) accRepo := account.NewRepository(masterDb) accPrefRepo := account_preference.NewRepository(masterDb) authRepo := user_auth.NewRepository(masterDb, authenticator, usrRepo, usrAccRepo, accPrefRepo) signupRepo := signup.NewRepository(masterDb, usrRepo, usrAccRepo, accRepo) - inviteRepo := invite.NewRepository(masterDb, usrRepo, usrAccRepo, accRepo, projectRoute.UserInviteAccept, notifyEmail, cfg.Project.SharedSecretKey) - prjRepo := project.NewRepository(masterDb) + inviteRepo := invite.NewRepository(masterDb, usrRepo, usrAccRepo, accRepo, webRoute.UserInviteAccept, notifyEmail, cfg.Project.SharedSecretKey) + chklstRepo := checklist.NewRepository(masterDb) appCtx := &handlers.AppContext{ Log: log, @@ -463,7 +463,7 @@ func main() { AuthRepo: authRepo, SignupRepo: signupRepo, InviteRepo: inviteRepo, - ProjectRepo: prjRepo, + ChecklistRepo: chklstRepo, Authenticator: authenticator, } diff --git a/cmd/web-api/tests/project_test.go b/cmd/web-api/tests/checklist_test.go similarity index 84% rename from cmd/web-api/tests/project_test.go rename to cmd/web-api/tests/checklist_test.go index 504cdd4..b4d57ab 100644 --- a/cmd/web-api/tests/project_test.go +++ b/cmd/web-api/tests/checklist_test.go @@ -8,26 +8,26 @@ import ( "testing" "time" + "geeks-accelerator/oss/saas-starter-kit/internal/checklist" "geeks-accelerator/oss/saas-starter-kit/internal/mid" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/tests" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" - "geeks-accelerator/oss/saas-starter-kit/internal/project" "github.com/pborman/uuid" ) -func mockProjectCreateRequest(accountID string) project.ProjectCreateRequest { - return project.ProjectCreateRequest{ +func mockChecklistCreateRequest(accountID string) checklist.ChecklistCreateRequest { + return checklist.ChecklistCreateRequest{ Name: fmt.Sprintf("Moon Launch %s", uuid.NewRandom().String()), AccountID: accountID, } } -// mockProject creates a new project for testing and associates it with the supplied account ID. -func newMockProject(accountID string) *project.Project { - req := mockProjectCreateRequest(accountID) - p, err := appCtx.ProjectRepo.Create(tests.Context(), auth.Claims{}, req, time.Now().UTC().AddDate(-1, -1, -1)) +// mockChecklist creates a new checklist for testing and associates it with the supplied account ID. +func newMockChecklist(accountID string) *checklist.Checklist { + req := mockChecklistCreateRequest(accountID) + p, err := appCtx.ChecklistRepo.Create(tests.Context(), auth.Claims{}, req, time.Now().UTC().AddDate(-1, -1, -1)) if err != nil { panic(err) } @@ -35,25 +35,25 @@ func newMockProject(accountID string) *project.Project { return p } -// TestProjectCRUDAdmin tests all the project CRUD endpoints using an user with role admin. -func TestProjectCRUDAdmin(t *testing.T) { +// TestChecklistCRUDAdmin tests all the checklist CRUD endpoints using an user with role admin. +func TestChecklistCRUDAdmin(t *testing.T) { defer tests.Recover(t) tr := roleTests[auth.RoleAdmin] - // Add claims to the context for the project. + // Add claims to the context for the checklist. ctx := context.WithValue(tests.Context(), auth.Key, tr.Claims) // Test create. - var created project.ProjectResponse + var created checklist.ChecklistResponse { expectedStatus := http.StatusCreated - req := mockProjectCreateRequest(tr.Account.ID) + req := mockChecklistCreateRequest(tr.Account.ID) rt := requestTest{ fmt.Sprintf("Create %d w/role %s", expectedStatus, tr.Role), http.MethodPost, - "/v1/projects", + "/v1/checklists", req, tr.Token, tr.Claims, @@ -68,7 +68,7 @@ func TestProjectCRUDAdmin(t *testing.T) { } t.Logf("\t%s\tReceived valid status code of %d.", tests.Success, w.Code) - var actual project.ProjectResponse + var actual checklist.ChecklistResponse if err := json.Unmarshal(w.Body.Bytes(), &actual); err != nil { t.Logf("\t\tGot error : %+v", err) t.Fatalf("\t%s\tDecode response body failed.", tests.Failed) @@ -79,12 +79,12 @@ func TestProjectCRUDAdmin(t *testing.T) { "updated_at": web.NewTimeResponse(ctx, actual.UpdatedAt.Value), "id": actual.ID, "account_id": req.AccountID, - "status": web.NewEnumResponse(ctx, "active", project.ProjectStatus_ValuesInterface()...), + "status": web.NewEnumResponse(ctx, "active", checklist.ChecklistStatus_ValuesInterface()...), "created_at": web.NewTimeResponse(ctx, actual.CreatedAt.Value), "name": req.Name, } - var expected project.ProjectResponse + var expected checklist.ChecklistResponse if err := decodeMapToStruct(expectedMap, &expected); err != nil { t.Logf("\t\tGot error : %+v\nActual results to format expected : \n", err) printResultMap(ctx, w.Body.Bytes()) // used to help format expectedMap @@ -107,7 +107,7 @@ func TestProjectCRUDAdmin(t *testing.T) { rt := requestTest{ fmt.Sprintf("Read %d w/role %s", expectedStatus, tr.Role), http.MethodGet, - fmt.Sprintf("/v1/projects/%s", created.ID), + fmt.Sprintf("/v1/checklists/%s", created.ID), nil, tr.Token, tr.Claims, @@ -122,7 +122,7 @@ func TestProjectCRUDAdmin(t *testing.T) { } t.Logf("\t%s\tReceived valid status code of %d.", tests.Success, w.Code) - var actual project.ProjectResponse + var actual checklist.ChecklistResponse if err := json.Unmarshal(w.Body.Bytes(), &actual); err != nil { t.Logf("\t\tGot error : %+v", err) t.Fatalf("\t%s\tDecode response body failed.", tests.Failed) @@ -142,7 +142,7 @@ func TestProjectCRUDAdmin(t *testing.T) { rt := requestTest{ fmt.Sprintf("Read %d w/role %s using random ID", expectedStatus, tr.Role), http.MethodGet, - fmt.Sprintf("/v1/projects/%s", randID), + fmt.Sprintf("/v1/checklists/%s", randID), nil, tr.Token, tr.Claims, @@ -166,7 +166,7 @@ func TestProjectCRUDAdmin(t *testing.T) { expected := weberror.ErrorResponse{ StatusCode: expectedStatus, Error: http.StatusText(expectedStatus), - Details: fmt.Sprintf("project %s not found: Entity not found", randID), + Details: fmt.Sprintf("checklist %s not found: Entity not found", randID), StackTrace: actual.StackTrace, } @@ -177,14 +177,14 @@ func TestProjectCRUDAdmin(t *testing.T) { } // Test Read with forbidden ID. - forbiddenProject := newMockProject(newMockSignup().account.ID) + forbiddenChecklist := newMockChecklist(newMockSignup().account.ID) { expectedStatus := http.StatusNotFound rt := requestTest{ fmt.Sprintf("Read %d w/role %s using forbidden ID", expectedStatus, tr.Role), http.MethodGet, - fmt.Sprintf("/v1/projects/%s", forbiddenProject.ID), + fmt.Sprintf("/v1/checklists/%s", forbiddenChecklist.ID), nil, tr.Token, tr.Claims, @@ -208,7 +208,7 @@ func TestProjectCRUDAdmin(t *testing.T) { expected := weberror.ErrorResponse{ StatusCode: expectedStatus, Error: http.StatusText(expectedStatus), - Details: fmt.Sprintf("project %s not found: Entity not found", forbiddenProject.ID), + Details: fmt.Sprintf("checklist %s not found: Entity not found", forbiddenChecklist.ID), StackTrace: actual.StackTrace, } @@ -226,8 +226,8 @@ func TestProjectCRUDAdmin(t *testing.T) { rt := requestTest{ fmt.Sprintf("Update %d w/role %s", expectedStatus, tr.Role), http.MethodPatch, - "/v1/projects", - project.ProjectUpdateRequest{ + "/v1/checklists", + checklist.ChecklistUpdateRequest{ ID: created.ID, Name: &newName, }, @@ -259,8 +259,8 @@ func TestProjectCRUDAdmin(t *testing.T) { rt := requestTest{ fmt.Sprintf("Archive %d w/role %s", expectedStatus, tr.Role), http.MethodPatch, - "/v1/projects/archive", - project.ProjectArchiveRequest{ + "/v1/checklists/archive", + checklist.ChecklistArchiveRequest{ ID: created.ID, }, tr.Token, @@ -291,7 +291,7 @@ func TestProjectCRUDAdmin(t *testing.T) { rt := requestTest{ fmt.Sprintf("Delete %d w/role %s", expectedStatus, tr.Role), http.MethodDelete, - fmt.Sprintf("/v1/projects/%s", created.ID), + fmt.Sprintf("/v1/checklists/%s", created.ID), nil, tr.Token, tr.Claims, @@ -315,24 +315,24 @@ func TestProjectCRUDAdmin(t *testing.T) { } } -// TestProjectCRUDUser tests all the project CRUD endpoints using an user with role project. -func TestProjectCRUDUser(t *testing.T) { +// TestChecklistCRUDUser tests all the checklist CRUD endpoints using an user with role checklist. +func TestChecklistCRUDUser(t *testing.T) { defer tests.Recover(t) tr := roleTests[auth.RoleUser] - // Add claims to the context for the project. + // Add claims to the context for the checklist. ctx := context.WithValue(tests.Context(), auth.Key, tr.Claims) // Test create. { expectedStatus := http.StatusForbidden - req := mockProjectCreateRequest(tr.Account.ID) + req := mockChecklistCreateRequest(tr.Account.ID) rt := requestTest{ fmt.Sprintf("Create %d w/role %s", expectedStatus, tr.Role), http.MethodPost, - "/v1/projects", + "/v1/checklists", req, tr.Token, tr.Claims, @@ -363,7 +363,7 @@ func TestProjectCRUDUser(t *testing.T) { } // Since role doesn't support create, bypass auth to test other endpoints. - created := newMockProject(tr.Account.ID).Response(ctx) + created := newMockChecklist(tr.Account.ID).Response(ctx) // Test read. { @@ -372,7 +372,7 @@ func TestProjectCRUDUser(t *testing.T) { rt := requestTest{ fmt.Sprintf("Read %d w/role %s", expectedStatus, tr.Role), http.MethodGet, - fmt.Sprintf("/v1/projects/%s", created.ID), + fmt.Sprintf("/v1/checklists/%s", created.ID), nil, tr.Token, tr.Claims, @@ -387,7 +387,7 @@ func TestProjectCRUDUser(t *testing.T) { } t.Logf("\t%s\tReceived valid status code of %d.", tests.Success, w.Code) - var actual *project.ProjectResponse + var actual *checklist.ChecklistResponse if err := json.Unmarshal(w.Body.Bytes(), &actual); err != nil { t.Logf("\t\tGot error : %+v", err) t.Fatalf("\t%s\tDecode response body failed.", tests.Failed) @@ -407,7 +407,7 @@ func TestProjectCRUDUser(t *testing.T) { rt := requestTest{ fmt.Sprintf("Read %d w/role %s using random ID", expectedStatus, tr.Role), http.MethodGet, - fmt.Sprintf("/v1/projects/%s", randID), + fmt.Sprintf("/v1/checklists/%s", randID), nil, tr.Token, tr.Claims, @@ -431,7 +431,7 @@ func TestProjectCRUDUser(t *testing.T) { expected := weberror.ErrorResponse{ StatusCode: expectedStatus, Error: http.StatusText(expectedStatus), - Details: fmt.Sprintf("project %s not found: Entity not found", randID), + Details: fmt.Sprintf("checklist %s not found: Entity not found", randID), StackTrace: actual.StackTrace, } @@ -442,14 +442,14 @@ func TestProjectCRUDUser(t *testing.T) { } // Test Read with forbidden ID. - forbiddenProject := newMockProject(newMockSignup().account.ID) + forbiddenChecklist := newMockChecklist(newMockSignup().account.ID) { expectedStatus := http.StatusNotFound rt := requestTest{ fmt.Sprintf("Read %d w/role %s using forbidden ID", expectedStatus, tr.Role), http.MethodGet, - fmt.Sprintf("/v1/projects/%s", forbiddenProject.ID), + fmt.Sprintf("/v1/checklists/%s", forbiddenChecklist.ID), nil, tr.Token, tr.Claims, @@ -473,7 +473,7 @@ func TestProjectCRUDUser(t *testing.T) { expected := weberror.ErrorResponse{ StatusCode: expectedStatus, Error: http.StatusText(expectedStatus), - Details: fmt.Sprintf("project %s not found: Entity not found", forbiddenProject.ID), + Details: fmt.Sprintf("checklist %s not found: Entity not found", forbiddenChecklist.ID), StackTrace: actual.StackTrace, } @@ -491,8 +491,8 @@ func TestProjectCRUDUser(t *testing.T) { rt := requestTest{ fmt.Sprintf("Update %d w/role %s", expectedStatus, tr.Role), http.MethodPatch, - "/v1/projects", - project.ProjectUpdateRequest{ + "/v1/checklists", + checklist.ChecklistUpdateRequest{ ID: created.ID, Name: &newName, }, @@ -531,8 +531,8 @@ func TestProjectCRUDUser(t *testing.T) { rt := requestTest{ fmt.Sprintf("Archive %d w/role %s", expectedStatus, tr.Role), http.MethodPatch, - "/v1/projects/archive", - project.ProjectArchiveRequest{ + "/v1/checklists/archive", + checklist.ChecklistArchiveRequest{ ID: created.ID, }, tr.Token, @@ -570,7 +570,7 @@ func TestProjectCRUDUser(t *testing.T) { rt := requestTest{ fmt.Sprintf("Delete %d w/role %s", expectedStatus, tr.Role), http.MethodDelete, - fmt.Sprintf("/v1/projects/%s", created.ID), + fmt.Sprintf("/v1/checklists/%s", created.ID), nil, tr.Token, tr.Claims, @@ -601,26 +601,26 @@ func TestProjectCRUDUser(t *testing.T) { } } -// TestProjectCreate validates create project endpoint. -func TestProjectCreate(t *testing.T) { +// TestChecklistCreate validates create checklist endpoint. +func TestChecklistCreate(t *testing.T) { defer tests.Recover(t) tr := roleTests[auth.RoleAdmin] - // Add claims to the context for the project. + // Add claims to the context for the checklist. ctx := context.WithValue(tests.Context(), auth.Key, tr.Claims) // Test create with invalid data. { expectedStatus := http.StatusBadRequest - req := mockProjectCreateRequest(tr.Account.ID) - invalidStatus := project.ProjectStatus("invalid status") + req := mockChecklistCreateRequest(tr.Account.ID) + invalidStatus := checklist.ChecklistStatus("invalid status") req.Status = &invalidStatus rt := requestTest{ fmt.Sprintf("Create %d w/role %s using invalid data", expectedStatus, tr.Role), http.MethodPost, - "/v1/projects", + "/v1/checklists", req, tr.Token, tr.Claims, @@ -646,7 +646,7 @@ func TestProjectCreate(t *testing.T) { Details: actual.Details, Error: "Field validation error", Fields: []weberror.FieldError{ - //{Field: "status", Error: "Key: 'ProjectCreateRequest.status' Error:Field validation for 'status' failed on the 'oneof' tag"}, + //{Field: "status", Error: "Key: 'ChecklistCreateRequest.status' Error:Field validation for 'status' failed on the 'oneof' tag"}, { Field: "status", Value: invalidStatus.String(), @@ -665,25 +665,25 @@ func TestProjectCreate(t *testing.T) { } } -// TestProjectUpdate validates update project endpoint. -func TestProjectUpdate(t *testing.T) { +// TestChecklistUpdate validates update checklist endpoint. +func TestChecklistUpdate(t *testing.T) { defer tests.Recover(t) tr := roleTests[auth.RoleAdmin] - // Add claims to the context for the project. + // Add claims to the context for the checklist. ctx := context.WithValue(tests.Context(), auth.Key, tr.Claims) // Test update with invalid data. { expectedStatus := http.StatusBadRequest - invalidStatus := project.ProjectStatus("invalid status") + invalidStatus := checklist.ChecklistStatus("invalid status") rt := requestTest{ fmt.Sprintf("Update %d w/role %s using invalid data", expectedStatus, tr.Role), http.MethodPatch, - "/v1/projects", - project.ProjectUpdateRequest{ + "/v1/checklists", + checklist.ChecklistUpdateRequest{ ID: uuid.NewRandom().String(), Status: &invalidStatus, }, @@ -711,7 +711,7 @@ func TestProjectUpdate(t *testing.T) { Details: actual.Details, Error: "Field validation error", Fields: []weberror.FieldError{ - //{Field: "status", Error: "Key: 'ProjectUpdateRequest.status' Error:Field validation for 'status' failed on the 'oneof' tag"}, + //{Field: "status", Error: "Key: 'ChecklistUpdateRequest.status' Error:Field validation for 'status' failed on the 'oneof' tag"}, { Field: "status", Value: invalidStatus.String(), @@ -730,16 +730,16 @@ func TestProjectUpdate(t *testing.T) { } } -// TestProjectArchive validates archive project endpoint. -func TestProjectArchive(t *testing.T) { +// TestChecklistArchive validates archive checklist endpoint. +func TestChecklistArchive(t *testing.T) { defer tests.Recover(t) tr := roleTests[auth.RoleAdmin] - // Add claims to the context for the project. + // Add claims to the context for the checklist. ctx := context.WithValue(tests.Context(), auth.Key, tr.Claims) - forbiddenProject := newMockProject(newMockSignup().account.ID) + forbiddenChecklist := newMockChecklist(newMockSignup().account.ID) // Test archive with invalid data. { @@ -750,8 +750,8 @@ func TestProjectArchive(t *testing.T) { rt := requestTest{ fmt.Sprintf("Archive %d w/role %s using invalid data", expectedStatus, tr.Role), http.MethodPatch, - "/v1/projects/archive", - project.ProjectArchiveRequest{ + "/v1/checklists/archive", + checklist.ChecklistArchiveRequest{ ID: invalidId, }, tr.Token, @@ -778,7 +778,7 @@ func TestProjectArchive(t *testing.T) { Details: actual.Details, Error: "Field validation error", Fields: []weberror.FieldError{ - //{Field: "id", Error: "Key: 'ProjectArchiveRequest.id' Error:Field validation for 'id' failed on the 'uuid' tag"}, + //{Field: "id", Error: "Key: 'ChecklistArchiveRequest.id' Error:Field validation for 'id' failed on the 'uuid' tag"}, { Field: "id", Value: invalidId, @@ -803,9 +803,9 @@ func TestProjectArchive(t *testing.T) { rt := requestTest{ fmt.Sprintf("Archive %d w/role %s using forbidden ID", expectedStatus, tr.Role), http.MethodPatch, - "/v1/projects/archive", - project.ProjectArchiveRequest{ - ID: forbiddenProject.ID, + "/v1/checklists/archive", + checklist.ChecklistArchiveRequest{ + ID: forbiddenChecklist.ID, }, tr.Token, tr.Claims, @@ -829,7 +829,7 @@ func TestProjectArchive(t *testing.T) { expected := weberror.ErrorResponse{ StatusCode: expectedStatus, Error: http.StatusText(expectedStatus), - Details: project.ErrForbidden.Error(), + Details: checklist.ErrForbidden.Error(), StackTrace: actual.StackTrace, } @@ -840,16 +840,16 @@ func TestProjectArchive(t *testing.T) { } } -// TestProjectDelete validates delete project endpoint. -func TestProjectDelete(t *testing.T) { +// TestChecklistDelete validates delete checklist endpoint. +func TestChecklistDelete(t *testing.T) { defer tests.Recover(t) tr := roleTests[auth.RoleAdmin] - // Add claims to the context for the project. + // Add claims to the context for the checklist. ctx := context.WithValue(tests.Context(), auth.Key, tr.Claims) - forbiddenProject := newMockProject(newMockSignup().account.ID) + forbiddenChecklist := newMockChecklist(newMockSignup().account.ID) // Test delete with invalid data. { @@ -860,7 +860,7 @@ func TestProjectDelete(t *testing.T) { rt := requestTest{ fmt.Sprintf("Delete %d w/role %s using invalid data", expectedStatus, tr.Role), http.MethodDelete, - "/v1/projects/" + invalidId, + "/v1/checklists/" + invalidId, nil, tr.Token, tr.Claims, @@ -911,7 +911,7 @@ func TestProjectDelete(t *testing.T) { rt := requestTest{ fmt.Sprintf("Delete %d w/role %s using forbidden ID", expectedStatus, tr.Role), http.MethodDelete, - fmt.Sprintf("/v1/projects/%s", forbiddenProject.ID), + fmt.Sprintf("/v1/checklists/%s", forbiddenChecklist.ID), nil, tr.Token, tr.Claims, @@ -935,7 +935,7 @@ func TestProjectDelete(t *testing.T) { expected := weberror.ErrorResponse{ StatusCode: expectedStatus, Error: http.StatusText(expectedStatus), - Details: project.ErrForbidden.Error(), + Details: checklist.ErrForbidden.Error(), StackTrace: actual.StackTrace, } diff --git a/cmd/web-api/tests/tests_test.go b/cmd/web-api/tests/tests_test.go index 4defad9..1d1fff3 100644 --- a/cmd/web-api/tests/tests_test.go +++ b/cmd/web-api/tests/tests_test.go @@ -17,19 +17,19 @@ import ( "geeks-accelerator/oss/saas-starter-kit/cmd/web-api/handlers" "geeks-accelerator/oss/saas-starter-kit/internal/account" "geeks-accelerator/oss/saas-starter-kit/internal/account/account_preference" + "geeks-accelerator/oss/saas-starter-kit/internal/checklist" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/notify" "geeks-accelerator/oss/saas-starter-kit/internal/platform/tests" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" - "geeks-accelerator/oss/saas-starter-kit/internal/project" - "geeks-accelerator/oss/saas-starter-kit/internal/project_route" "geeks-accelerator/oss/saas-starter-kit/internal/signup" "geeks-accelerator/oss/saas-starter-kit/internal/user" "geeks-accelerator/oss/saas-starter-kit/internal/user_account" "geeks-accelerator/oss/saas-starter-kit/internal/user_account/invite" "geeks-accelerator/oss/saas-starter-kit/internal/user_auth" + "geeks-accelerator/oss/saas-starter-kit/internal/webroute" "github.com/google/go-cmp/cmp" "github.com/iancoleman/strcase" "github.com/pborman/uuid" @@ -93,7 +93,7 @@ func testMain(m *testing.M) int { log := test.Log log.SetOutput(ioutil.Discard) - projectRoute, err := project_route.New("http://web-api.com", "http://web-app.com") + projectRoute, err := webroute.New("http://web-api.com", "http://web-app.com") if err != nil { panic(err) } @@ -107,7 +107,7 @@ func testMain(m *testing.M) int { authRepo := user_auth.NewRepository(test.MasterDB, authenticator, usrRepo, usrAccRepo, accPrefRepo) signupRepo := signup.NewRepository(test.MasterDB, usrRepo, usrAccRepo, accRepo) inviteRepo := invite.NewRepository(test.MasterDB, usrRepo, usrAccRepo, accRepo, projectRoute.UserInviteAccept, notifyEmail, "6368616e676520746869732070613434") - prjRepo := project.NewRepository(test.MasterDB) + prjRepo := checklist.NewRepository(test.MasterDB) appCtx = &handlers.AppContext{ Log: log, diff --git a/cmd/web-app/handlers/projects.go b/cmd/web-app/handlers/checklists.go similarity index 61% rename from cmd/web-app/handlers/projects.go rename to cmd/web-app/handlers/checklists.go index 3075e9a..1c1ac7d 100644 --- a/cmd/web-app/handlers/projects.go +++ b/cmd/web-app/handlers/checklists.go @@ -6,50 +6,50 @@ import ( "net/http" "strings" + "geeks-accelerator/oss/saas-starter-kit/internal/checklist" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/datatable" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" - "geeks-accelerator/oss/saas-starter-kit/internal/project" "github.com/gorilla/schema" "github.com/pkg/errors" "gopkg.in/DataDog/dd-trace-go.v1/contrib/go-redis/redis" ) -// Projects represents the Projects API method handler set. -type Projects struct { - ProjectRepo *project.Repository - Redis *redis.Client - Renderer web.Renderer +// Checklists represents the Checklists API method handler set. +type Checklists struct { + ChecklistRepo *checklist.Repository + Redis *redis.Client + Renderer web.Renderer } -func urlProjectsIndex() string { - return fmt.Sprintf("/projects") +func urlChecklistsIndex() string { + return fmt.Sprintf("/checklists") } -func urlProjectsCreate() string { - return fmt.Sprintf("/projects/create") +func urlChecklistsCreate() string { + return fmt.Sprintf("/checklists/create") } -func urlProjectsView(projectID string) string { - return fmt.Sprintf("/projects/%s", projectID) +func urlChecklistsView(checklistID string) string { + return fmt.Sprintf("/checklists/%s", checklistID) } -func urlProjectsUpdate(projectID string) string { - return fmt.Sprintf("/projects/%s/update", projectID) +func urlChecklistsUpdate(checklistID string) string { + return fmt.Sprintf("/checklists/%s/update", checklistID) } -// Index handles listing all the projects for the current account. -func (h *Projects) Index(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +// Index handles listing all the checklists for the current account. +func (h *Checklists) Index(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { claims, err := auth.ClaimsFromContext(ctx) if err != nil { return err } - statusOpts := web.NewEnumResponse(ctx, nil, project.ProjectStatus_ValuesInterface()...) + statusOpts := web.NewEnumResponse(ctx, nil, checklist.ChecklistStatus_ValuesInterface()...) statusFilterItems := []datatable.FilterOptionItem{} for _, opt := range statusOpts.Options { @@ -61,13 +61,13 @@ func (h *Projects) Index(ctx context.Context, w http.ResponseWriter, r *http.Req fields := []datatable.DisplayField{ datatable.DisplayField{Field: "id", Title: "ID", Visible: false, Searchable: true, Orderable: true, Filterable: false}, - datatable.DisplayField{Field: "name", Title: "Project", Visible: true, Searchable: true, Orderable: true, Filterable: true, FilterPlaceholder: "filter Name"}, + datatable.DisplayField{Field: "name", Title: "Checklist", Visible: true, Searchable: true, Orderable: true, Filterable: true, FilterPlaceholder: "filter Name"}, datatable.DisplayField{Field: "status", Title: "Status", Visible: true, Searchable: true, Orderable: true, Filterable: true, FilterPlaceholder: "All Statuses", FilterItems: statusFilterItems}, datatable.DisplayField{Field: "updated_at", Title: "Last Updated", Visible: true, Searchable: true, Orderable: true, Filterable: false}, datatable.DisplayField{Field: "created_at", Title: "Created", Visible: true, Searchable: true, Orderable: true, Filterable: false}, } - mapFunc := func(q *project.Project, cols []datatable.DisplayField) (resp []datatable.ColumnValue, err error) { + mapFunc := func(q *checklist.Checklist, cols []datatable.DisplayField) (resp []datatable.ColumnValue, err error) { for i := 0; i < len(cols); i++ { col := cols[i] var v datatable.ColumnValue @@ -76,17 +76,17 @@ func (h *Projects) Index(ctx context.Context, w http.ResponseWriter, r *http.Req v.Value = fmt.Sprintf("%s", q.ID) case "name": v.Value = q.Name - v.Formatted = fmt.Sprintf("%s", urlProjectsView(q.ID), v.Value) + v.Formatted = fmt.Sprintf("%s", urlChecklistsView(q.ID), v.Value) case "status": v.Value = q.Status.String() var subStatusClass string var subStatusIcon string switch q.Status { - case project.ProjectStatus_Active: + case checklist.ChecklistStatus_Active: subStatusClass = "text-green" subStatusIcon = "far fa-dot-circle" - case project.ProjectStatus_Disabled: + case checklist.ChecklistStatus_Disabled: subStatusClass = "text-orange" subStatusIcon = "far fa-circle" } @@ -110,7 +110,7 @@ func (h *Projects) Index(ctx context.Context, w http.ResponseWriter, r *http.Req } loadFunc := func(ctx context.Context, sorting string, fields []datatable.DisplayField) (resp [][]datatable.ColumnValue, err error) { - res, err := h.ProjectRepo.Find(ctx, claims, project.ProjectFindRequest{ + res, err := h.ChecklistRepo.Find(ctx, claims, checklist.ChecklistFindRequest{ Where: "account_id = ?", Args: []interface{}{claims.Audience}, Order: strings.Split(sorting, ","), @@ -122,7 +122,7 @@ func (h *Projects) Index(ctx context.Context, w http.ResponseWriter, r *http.Req for _, a := range res { l, err := mapFunc(a, fields) if err != nil { - return resp, errors.Wrapf(err, "Failed to map project for display.") + return resp, errors.Wrapf(err, "Failed to map checklist for display.") } resp = append(resp, l) @@ -148,15 +148,15 @@ func (h *Projects) Index(ctx context.Context, w http.ResponseWriter, r *http.Req } data := map[string]interface{}{ - "datatable": dt.Response(), - "urlProjectsCreate": urlProjectsCreate(), + "datatable": dt.Response(), + "urlChecklistsCreate": urlChecklistsCreate(), } - return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "projects-index.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data) + return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "checklists-index.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data) } -// Create handles creating a new project for the account. -func (h *Projects) Create(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +// Create handles creating a new checklist for the account. +func (h *Checklists) Create(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { ctxValues, err := webcontext.ContextValues(ctx) if err != nil { @@ -169,7 +169,7 @@ func (h *Projects) Create(ctx context.Context, w http.ResponseWriter, r *http.Re } // - req := new(project.ProjectCreateRequest) + req := new(checklist.ChecklistCreateRequest) data := make(map[string]interface{}) f := func() (bool, error) { if r.Method == http.MethodPost { @@ -186,7 +186,7 @@ func (h *Projects) Create(ctx context.Context, w http.ResponseWriter, r *http.Re } req.AccountID = claims.Audience - usr, err := h.ProjectRepo.Create(ctx, claims, *req, ctxValues.Now) + usr, err := h.ChecklistRepo.Create(ctx, claims, *req, ctxValues.Now) if err != nil { switch errors.Cause(err) { default: @@ -199,12 +199,12 @@ func (h *Projects) Create(ctx context.Context, w http.ResponseWriter, r *http.Re } } - // Display a success message to the project. + // Display a success message to the checklist. webcontext.SessionFlashSuccess(ctx, - "Project Created", - "Project successfully created.") + "Checklist Created", + "Checklist successfully created.") - return true, web.Redirect(ctx, w, r, urlProjectsView(usr.ID), http.StatusFound) + return true, web.Redirect(ctx, w, r, urlChecklistsView(usr.ID), http.StatusFound) } return false, nil @@ -219,17 +219,17 @@ func (h *Projects) Create(ctx context.Context, w http.ResponseWriter, r *http.Re data["form"] = req - if verr, ok := weberror.NewValidationError(ctx, webcontext.Validator().Struct(project.ProjectCreateRequest{})); ok { + if verr, ok := weberror.NewValidationError(ctx, webcontext.Validator().Struct(checklist.ChecklistCreateRequest{})); ok { data["validationDefaults"] = verr.(*weberror.Error) } - return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "projects-create.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data) + return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "checklists-create.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data) } -// View handles displaying a project. -func (h *Projects) View(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +// View handles displaying a checklist. +func (h *Checklists) View(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { - projectID := params["project_id"] + checklistID := params["checklist_id"] ctxValues, err := webcontext.ContextValues(ctx) if err != nil { @@ -251,18 +251,18 @@ func (h *Projects) View(ctx context.Context, w http.ResponseWriter, r *http.Requ switch r.PostForm.Get("action") { case "archive": - err = h.ProjectRepo.Archive(ctx, claims, project.ProjectArchiveRequest{ - ID: projectID, + err = h.ChecklistRepo.Archive(ctx, claims, checklist.ChecklistArchiveRequest{ + ID: checklistID, }, ctxValues.Now) if err != nil { return false, err } webcontext.SessionFlashSuccess(ctx, - "Project Archive", - "Project successfully archive.") + "Checklist Archive", + "Checklist successfully archive.") - return true, web.Redirect(ctx, w, r, urlProjectsIndex(), http.StatusFound) + return true, web.Redirect(ctx, w, r, urlChecklistsIndex(), http.StatusFound) } } @@ -276,21 +276,21 @@ func (h *Projects) View(ctx context.Context, w http.ResponseWriter, r *http.Requ return nil } - prj, err := h.ProjectRepo.ReadByID(ctx, claims, projectID) + prj, err := h.ChecklistRepo.ReadByID(ctx, claims, checklistID) if err != nil { return err } - data["project"] = prj.Response(ctx) - data["urlProjectsView"] = urlProjectsView(projectID) - data["urlProjectsUpdate"] = urlProjectsUpdate(projectID) + data["checklist"] = prj.Response(ctx) + data["urlChecklistsView"] = urlChecklistsView(checklistID) + data["urlChecklistsUpdate"] = urlChecklistsUpdate(checklistID) - return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "projects-view.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data) + return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "checklists-view.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data) } -// Update handles updating a project for the account. -func (h *Projects) Update(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { +// Update handles updating a checklist for the account. +func (h *Checklists) Update(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error { - projectID := params["project_id"] + checklistID := params["checklist_id"] ctxValues, err := webcontext.ContextValues(ctx) if err != nil { @@ -303,7 +303,7 @@ func (h *Projects) Update(ctx context.Context, w http.ResponseWriter, r *http.Re } // - req := new(project.ProjectUpdateRequest) + req := new(checklist.ChecklistUpdateRequest) data := make(map[string]interface{}) f := func() (bool, error) { if r.Method == http.MethodPost { @@ -318,9 +318,9 @@ func (h *Projects) Update(ctx context.Context, w http.ResponseWriter, r *http.Re if err := decoder.Decode(req, r.PostForm); err != nil { return false, err } - req.ID = projectID + req.ID = checklistID - err = h.ProjectRepo.Update(ctx, claims, *req, ctxValues.Now) + err = h.ChecklistRepo.Update(ctx, claims, *req, ctxValues.Now) if err != nil { switch errors.Cause(err) { default: @@ -333,12 +333,12 @@ func (h *Projects) Update(ctx context.Context, w http.ResponseWriter, r *http.Re } } - // Display a success message to the project. + // Display a success message to the checklist. webcontext.SessionFlashSuccess(ctx, - "Project Updated", - "Project successfully updated.") + "Checklist Updated", + "Checklist successfully updated.") - return true, web.Redirect(ctx, w, r, urlProjectsView(req.ID), http.StatusFound) + return true, web.Redirect(ctx, w, r, urlChecklistsView(req.ID), http.StatusFound) } return false, nil @@ -351,13 +351,13 @@ func (h *Projects) Update(ctx context.Context, w http.ResponseWriter, r *http.Re return nil } - prj, err := h.ProjectRepo.ReadByID(ctx, claims, projectID) + prj, err := h.ChecklistRepo.ReadByID(ctx, claims, checklistID) if err != nil { return err } - data["project"] = prj.Response(ctx) + data["checklist"] = prj.Response(ctx) - data["urlProjectsView"] = urlProjectsView(projectID) + data["urlChecklistsView"] = urlChecklistsView(checklistID) if req.ID == "" { req.Name = &prj.Name @@ -365,9 +365,9 @@ func (h *Projects) Update(ctx context.Context, w http.ResponseWriter, r *http.Re } data["form"] = req - if verr, ok := weberror.NewValidationError(ctx, webcontext.Validator().Struct(project.ProjectUpdateRequest{})); ok { + if verr, ok := weberror.NewValidationError(ctx, webcontext.Validator().Struct(checklist.ChecklistUpdateRequest{})); ok { data["validationDefaults"] = verr.(*weberror.Error) } - return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "projects-update.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data) + return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "checklists-update.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data) } diff --git a/cmd/web-app/handlers/root.go b/cmd/web-app/handlers/root.go index d31b6c7..9940dab 100644 --- a/cmd/web-app/handlers/root.go +++ b/cmd/web-app/handlers/root.go @@ -8,7 +8,7 @@ import ( "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" - "geeks-accelerator/oss/saas-starter-kit/internal/project_route" + "geeks-accelerator/oss/saas-starter-kit/internal/webroute" "github.com/ikeikeikeike/go-sitemap-generator/v2/stm" "github.com/pkg/errors" @@ -19,9 +19,9 @@ import ( // Root represents the Root API method handler set. type Root struct { - Renderer web.Renderer - Sitemap *stm.Sitemap - ProjectRoute project_route.ProjectRoute + Renderer web.Renderer + Sitemap *stm.Sitemap + WebRoute webroute.WebRoute } // Index determines if the user has authentication and loads the associated page. @@ -56,7 +56,7 @@ func (h *Root) SitePage(ctx context.Context, w http.ResponseWriter, r *http.Requ tmpName = "site-api.gohtml" // http://127.0.0.1:3001/docs/doc.json - swaggerJsonUrl := h.ProjectRoute.ApiDocsJson(true) + swaggerJsonUrl := h.WebRoute.ApiDocsJson(true) // Load the json file from the API service. res, err := pester.Get(swaggerJsonUrl) @@ -92,8 +92,8 @@ func (h *Root) SitePage(ctx context.Context, w http.ResponseWriter, r *http.Requ return errors.WithStack(err) } - data["urlApiBaseUri"] = h.ProjectRoute.WebApiUrl(doc.BasePath) - data["urlApiDocs"] = h.ProjectRoute.ApiDocs() + data["urlApiBaseUri"] = h.WebRoute.WebApiUrl(doc.BasePath) + data["urlApiDocs"] = h.WebRoute.ApiDocs() case "/pricing": tmpName = "site-pricing.gohtml" @@ -122,7 +122,7 @@ func (h *Root) RobotTxt(ctx context.Context, w http.ResponseWriter, r *http.Requ return web.RespondText(ctx, w, txt, http.StatusOK) } - sitemapUrl := h.ProjectRoute.WebAppUrl("/sitemap.xml") + sitemapUrl := h.WebRoute.WebAppUrl("/sitemap.xml") txt := fmt.Sprintf("User-agent: *\nDisallow: /ping\nDisallow: /status\nDisallow: /debug/\nSitemap: %s", sitemapUrl) return web.RespondText(ctx, w, txt, http.StatusOK) diff --git a/cmd/web-app/handlers/routes.go b/cmd/web-app/handlers/routes.go index b0df47b..75158ed 100644 --- a/cmd/web-app/handlers/routes.go +++ b/cmd/web-app/handlers/routes.go @@ -11,19 +11,19 @@ import ( "geeks-accelerator/oss/saas-starter-kit/internal/account" "geeks-accelerator/oss/saas-starter-kit/internal/account/account_preference" + "geeks-accelerator/oss/saas-starter-kit/internal/checklist" "geeks-accelerator/oss/saas-starter-kit/internal/geonames" "geeks-accelerator/oss/saas-starter-kit/internal/mid" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" - "geeks-accelerator/oss/saas-starter-kit/internal/project" - "geeks-accelerator/oss/saas-starter-kit/internal/project_route" "geeks-accelerator/oss/saas-starter-kit/internal/signup" "geeks-accelerator/oss/saas-starter-kit/internal/user" "geeks-accelerator/oss/saas-starter-kit/internal/user_account" "geeks-accelerator/oss/saas-starter-kit/internal/user_account/invite" "geeks-accelerator/oss/saas-starter-kit/internal/user_auth" + "geeks-accelerator/oss/saas-starter-kit/internal/webroute" "github.com/aws/aws-sdk-go/aws/session" "github.com/ikeikeikeike/go-sitemap-generator/v2/stm" @@ -50,13 +50,13 @@ type AppContext struct { AuthRepo *user_auth.Repository SignupRepo *signup.Repository InviteRepo *invite.Repository - ProjectRepo *project.Repository + ChecklistRepo *checklist.Repository GeoRepo *geonames.Repository Authenticator *auth.Authenticator StaticDir string TemplateDir string Renderer web.Renderer - ProjectRoute project_route.ProjectRoute + WebRoute webroute.WebRoute PreAppMiddleware []web.Middleware PostAppMiddleware []web.Middleware AwsSession *session.Session @@ -105,7 +105,7 @@ func APP(shutdown chan os.Signal, appCtx *AppContext) http.Handler { // Build a sitemap. sm := stm.NewSitemap(1) sm.SetVerbose(false) - sm.SetDefaultHost(appCtx.ProjectRoute.WebAppUrl("")) + sm.SetDefaultHost(appCtx.WebRoute.WebAppUrl("")) sm.Create() smLocAddModified := func(loc stm.URL, filename string) { @@ -121,19 +121,19 @@ func APP(shutdown chan os.Signal, appCtx *AppContext) http.Handler { sm.Add(loc) } - // Register project management pages. - p := Projects{ - ProjectRepo: appCtx.ProjectRepo, - Redis: appCtx.Redis, - Renderer: appCtx.Renderer, + // Register checklist management pages. + p := Checklists{ + ChecklistRepo: appCtx.ChecklistRepo, + Redis: appCtx.Redis, + Renderer: appCtx.Renderer, } - app.Handle("POST", "/projects/:project_id/update", p.Update, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) - app.Handle("GET", "/projects/:project_id/update", p.Update, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) - app.Handle("POST", "/projects/:project_id", p.View, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) - app.Handle("GET", "/projects/:project_id", p.View, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth()) - app.Handle("POST", "/projects/create", p.Create, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) - app.Handle("GET", "/projects/create", p.Create, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) - app.Handle("GET", "/projects", p.Index, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth()) + app.Handle("POST", "/checklists/:checklist_id/update", p.Update, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) + app.Handle("GET", "/checklists/:checklist_id/update", p.Update, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) + app.Handle("POST", "/checklists/:checklist_id", p.View, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) + app.Handle("GET", "/checklists/:checklist_id", p.View, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth()) + app.Handle("POST", "/checklists/create", p.Create, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) + app.Handle("GET", "/checklists/create", p.Create, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasRole(auth.RoleAdmin)) + app.Handle("GET", "/checklists", p.Index, mid.AuthenticateSessionRequired(appCtx.Authenticator), mid.HasAuth()) // Register user management pages. us := Users{ @@ -231,9 +231,9 @@ func APP(shutdown chan os.Signal, appCtx *AppContext) http.Handler { // Register root r := Root{ - Renderer: appCtx.Renderer, - ProjectRoute: appCtx.ProjectRoute, - Sitemap: sm, + Renderer: appCtx.Renderer, + WebRoute: appCtx.WebRoute, + Sitemap: sm, } app.Handle("GET", "/api", r.SitePage) app.Handle("GET", "/pricing", r.SitePage) diff --git a/cmd/web-app/main.go b/cmd/web-app/main.go index 31674d5..219ab63 100644 --- a/cmd/web-app/main.go +++ b/cmd/web-app/main.go @@ -23,6 +23,7 @@ import ( "geeks-accelerator/oss/saas-starter-kit/cmd/web-app/handlers" "geeks-accelerator/oss/saas-starter-kit/internal/account" "geeks-accelerator/oss/saas-starter-kit/internal/account/account_preference" + "geeks-accelerator/oss/saas-starter-kit/internal/checklist" "geeks-accelerator/oss/saas-starter-kit/internal/geonames" "geeks-accelerator/oss/saas-starter-kit/internal/mid" "geeks-accelerator/oss/saas-starter-kit/internal/platform/auth" @@ -30,16 +31,15 @@ import ( img_resize "geeks-accelerator/oss/saas-starter-kit/internal/platform/img-resize" "geeks-accelerator/oss/saas-starter-kit/internal/platform/notify" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" - template_renderer "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/template-renderer" + template_renderer "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/tmplrender" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext" "geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror" - "geeks-accelerator/oss/saas-starter-kit/internal/project" - "geeks-accelerator/oss/saas-starter-kit/internal/project_route" "geeks-accelerator/oss/saas-starter-kit/internal/signup" "geeks-accelerator/oss/saas-starter-kit/internal/user" "geeks-accelerator/oss/saas-starter-kit/internal/user_account" "geeks-accelerator/oss/saas-starter-kit/internal/user_account/invite" "geeks-accelerator/oss/saas-starter-kit/internal/user_auth" + "geeks-accelerator/oss/saas-starter-kit/internal/webroute" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/credentials" @@ -440,20 +440,20 @@ func main() { // ========================================================================= // Init repositories and AppContext - projectRoute, err := project_route.New(cfg.Project.WebApiBaseUrl, cfg.Service.BaseUrl) + webRoute, err := webroute.New(cfg.Project.WebApiBaseUrl, cfg.Service.BaseUrl) if err != nil { - log.Fatalf("main : project routes : %+v", cfg.Service.BaseUrl, err) + log.Fatalf("main : checklist routes : %+v", cfg.Service.BaseUrl, err) } - usrRepo := user.NewRepository(masterDb, projectRoute.UserResetPassword, notifyEmail, cfg.Project.SharedSecretKey) + usrRepo := user.NewRepository(masterDb, webRoute.UserResetPassword, notifyEmail, cfg.Project.SharedSecretKey) usrAccRepo := user_account.NewRepository(masterDb) accRepo := account.NewRepository(masterDb) geoRepo := geonames.NewRepository(masterDb) accPrefRepo := account_preference.NewRepository(masterDb) authRepo := user_auth.NewRepository(masterDb, authenticator, usrRepo, usrAccRepo, accPrefRepo) signupRepo := signup.NewRepository(masterDb, usrRepo, usrAccRepo, accRepo) - inviteRepo := invite.NewRepository(masterDb, usrRepo, usrAccRepo, accRepo, projectRoute.UserInviteAccept, notifyEmail, cfg.Project.SharedSecretKey) - prjRepo := project.NewRepository(masterDb) + inviteRepo := invite.NewRepository(masterDb, usrRepo, usrAccRepo, accRepo, webRoute.UserInviteAccept, notifyEmail, cfg.Project.SharedSecretKey) + chklstRepo := checklist.NewRepository(masterDb) appCtx := &handlers.AppContext{ Log: log, @@ -463,7 +463,7 @@ func main() { Redis: redisClient, TemplateDir: cfg.Service.TemplateDir, StaticDir: cfg.Service.StaticFiles.Dir, - ProjectRoute: projectRoute, + WebRoute: webRoute, UserRepo: usrRepo, UserAccountRepo: usrAccRepo, AccountRepo: accRepo, @@ -472,7 +472,7 @@ func main() { GeoRepo: geoRepo, SignupRepo: signupRepo, InviteRepo: inviteRepo, - ProjectRepo: prjRepo, + ChecklistRepo: chklstRepo, Authenticator: authenticator, AwsSession: awsSession, } @@ -525,13 +525,14 @@ func main() { staticS3UrlFormatter = func(p string) string { // When the path starts with a forward slash its referencing a local file, // make sure the static file prefix is included - if strings.HasPrefix(p, "/") || !strings.HasPrefix(p, "://") { + if (strings.HasPrefix(p, "/") || !strings.HasPrefix(p, "://")) && !strings.HasPrefix(p, cfg.Service.StaticFiles.S3Prefix) { p = filepath.Join(cfg.Service.StaticFiles.S3Prefix, p) } + return s3UrlFormatter(p) } } else { - staticS3UrlFormatter = projectRoute.WebAppUrl + staticS3UrlFormatter = webRoute.WebAppUrl } // staticUrlFormatter is a help function used by template functions defined below. @@ -920,11 +921,12 @@ func main() { tmplFuncs["S3ImgUrl"] = func(ctx context.Context, p string, size int) string { imgUrl := imgUrlFormatter(p) if cfg.Service.StaticFiles.ImgResizeEnabled { + var rerr error imgUrl, rerr = img_resize.S3ImgUrl(ctx, redisClient, staticS3UrlFormatter, awsSession, cfg.Aws.S3BucketPublic, imgResizeS3KeyPrefix, imgUrl, size) if rerr != nil { imgUrl = "error" - log.Printf("main : S3ImgUrl : %s - %s\n", p, rerr) + log.Printf("main : S3ImgUrl : %s - %+v\n", p, rerr) } } return imgUrl diff --git a/cmd/web-app/templates/content/projects-create.gohtml b/cmd/web-app/templates/content/checklists-create.gohtml similarity index 76% rename from cmd/web-app/templates/content/projects-create.gohtml rename to cmd/web-app/templates/content/checklists-create.gohtml index 3b498fc..1250a51 100644 --- a/cmd/web-app/templates/content/projects-create.gohtml +++ b/cmd/web-app/templates/content/checklists-create.gohtml @@ -1,4 +1,4 @@ -{{define "title"}}Create Project{{end}} +{{define "title"}}Create Checklist{{end}} {{define "style"}} {{end}} @@ -6,13 +6,13 @@
-

Create Project

+

Create Checklist

@@ -23,10 +23,10 @@
- + + placeholder="Enter name for your checklist" name="Name"value="{{ .form.Name }}" required> {{template "invalid-feedback" dict "fieldName" "Name" "validationDefaults" $.validationDefaults "validationErrors" $.validationErrors }}
@@ -37,7 +37,7 @@ diff --git a/cmd/web-app/templates/content/projects-index.gohtml b/cmd/web-app/templates/content/checklists-index.gohtml similarity index 76% rename from cmd/web-app/templates/content/projects-index.gohtml rename to cmd/web-app/templates/content/checklists-index.gohtml index bfc52da..96c0e82 100644 --- a/cmd/web-app/templates/content/projects-index.gohtml +++ b/cmd/web-app/templates/content/checklists-index.gohtml @@ -1,19 +1,19 @@ -{{define "title"}}Projects{{end}} +{{define "title"}}Checklists{{end}} {{define "content"}}
-

Projects

+

Checklists

{{ if HasRole $._Ctx "admin" }} - - Create Project + + Create Checklist {{ end }}
diff --git a/cmd/web-app/templates/content/projects-update.gohtml b/cmd/web-app/templates/content/checklists-update.gohtml similarity index 83% rename from cmd/web-app/templates/content/projects-update.gohtml rename to cmd/web-app/templates/content/checklists-update.gohtml index 974bbf5..5059b8f 100644 --- a/cmd/web-app/templates/content/projects-update.gohtml +++ b/cmd/web-app/templates/content/checklists-update.gohtml @@ -1,4 +1,4 @@ -{{define "title"}}Update Project - {{ .project.Name }}{{end}} +{{define "title"}}Update Checklist - {{ .checklist.Name }}{{end}} {{define "style"}} {{end}} @@ -6,14 +6,14 @@
-

Update Project

+

Update Checklist

@@ -22,7 +22,7 @@
-

Project Details

+

Checklist Details

@@ -39,7 +39,7 @@ diff --git a/cmd/web-app/templates/content/projects-view.gohtml b/cmd/web-app/templates/content/checklists-view.gohtml similarity index 66% rename from cmd/web-app/templates/content/projects-view.gohtml rename to cmd/web-app/templates/content/checklists-view.gohtml index 42dbc0d..acf7339 100644 --- a/cmd/web-app/templates/content/projects-view.gohtml +++ b/cmd/web-app/templates/content/checklists-view.gohtml @@ -1,4 +1,4 @@ -{{define "title"}}Project - {{ .project.Name }}{{end}} +{{define "title"}}Checklist - {{ .checklist.Name }}{{end}} {{define "style"}} {{end}} @@ -6,29 +6,29 @@
-

{{ .project.Name }}

- +

{{ .checklist.Name }}

+
-
Project Details
+
Checklist Details
@@ -38,16 +38,16 @@

Name
- {{ .project.Name }} + {{ .checklist.Name }}

Status
- {{ if .project }} + {{ if .checklist }} - {{ if eq .project.Status.Value "active" }} - {{ .project.Status.Title }} + {{ if eq .checklist.Status.Value "active" }} + {{ .checklist.Status.Title }} {{else}} - {{.project.Status.Title }} + {{.checklist.Status.Title }} {{end}} {{ end }} @@ -56,7 +56,7 @@

ID
- {{ .project.ID }} + {{ .checklist.ID }}

diff --git a/cmd/web-app/templates/content/examples-images.gohtml b/cmd/web-app/templates/content/examples-images.gohtml index b258203..51129e8 100644 --- a/cmd/web-app/templates/content/examples-images.gohtml +++ b/cmd/web-app/templates/content/examples-images.gohtml @@ -10,7 +10,7 @@

AWS credentials must be set and then the following configs be set as well.

-            export WEB_APP_SERVICE_S3_BUCKET_PUBLIC=example-bucket-public
+            export WEB_APP_AWS_S3_BUCKET_PUBLIC=example-bucket-public
             export WEB_APP_SERVICE_STATICFILES_IMG_RESIZE_ENABLED=1
         
diff --git a/cmd/web-app/templates/partials/app-sidebar.tmpl b/cmd/web-app/templates/partials/app-sidebar.tmpl index b840c30..f8582ac 100644 --- a/cmd/web-app/templates/partials/app-sidebar.tmpl +++ b/cmd/web-app/templates/partials/app-sidebar.tmpl @@ -7,7 +7,7 @@ - + {{ if HasAuth $._Ctx }} @@ -32,13 +32,13 @@ diff --git a/docker-compose.yaml b/docker-compose.yaml index 4eb9795..7e67634 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -36,7 +36,7 @@ services: entrypoint: redis-server --appendonly yes datadog: - image: example-project/datadog:latest + image: example-checklist/datadog:latest build: context: build/docker/datadog-agent dockerfile: Dockerfile @@ -60,7 +60,7 @@ services: - DD_DOGSTATSD_NON_LOCAL_TRAFFIC=true - DD_EXPVAR=service_name=web-app env=dev url=http://web-app:4000/debug/vars|service_name=web-api env=dev url=http://web-api:4001/debug/vars web-app: - image: example-project/web-app:latest + image: example-checklist/web-app:latest build: context: . target: dev @@ -102,7 +102,7 @@ services: # - GODEBUG=gctrace=1 web-api: - image: example-project/web-api:latest + image: example-checklist/web-api:latest build: context: . target: dev diff --git a/internal/project/project.go b/internal/checklist/checklist.go similarity index 69% rename from internal/project/project.go rename to internal/checklist/checklist.go index b130702..59f7381 100644 --- a/internal/project/project.go +++ b/internal/checklist/checklist.go @@ -1,4 +1,4 @@ -package project +package checklist import ( "context" @@ -15,8 +15,8 @@ import ( ) const ( - // The database table for Project - projectTableName = "projects" + // The database table for Checklist + checklistTableName = "checklists" ) var ( @@ -27,14 +27,14 @@ var ( ErrForbidden = errors.New("Attempted action is not allowed") ) -// CanReadProject determines if claims has the authority to access the specified project by id. -func (repo *Repository) CanReadProject(ctx context.Context, claims auth.Claims, id string) error { +// CanReadChecklist determines if claims has the authority to access the specified checklist by id. +func (repo *Repository) CanReadChecklist(ctx context.Context, claims auth.Claims, id string) error { - // If the request has claims from a specific project, ensure that the claims - // has the correct access to the project. + // If the request has claims from a specific checklist, ensure that the claims + // has the correct access to the checklist. if claims.Audience != "" { - // select id from projects where account_id = [accountID] - query := sqlbuilder.NewSelectBuilder().Select("id").From(projectTableName) + // select id from checklists where account_id = [accountID] + query := sqlbuilder.NewSelectBuilder().Select("id").From(checklistTableName) query.Where(query.And( query.Equal("account_id", claims.Audience), query.Equal("ID", id), @@ -50,7 +50,7 @@ func (repo *Repository) CanReadProject(ctx context.Context, claims auth.Claims, } // When there is no id returned, then the current claim user does not have access - // to the specified project. + // to the specified checklist. if id == "" { return errors.WithStack(ErrForbidden) } @@ -60,14 +60,14 @@ func (repo *Repository) CanReadProject(ctx context.Context, claims auth.Claims, return nil } -// CanModifyProject determines if claims has the authority to modify the specified project by id. -func (repo *Repository) CanModifyProject(ctx context.Context, claims auth.Claims, id string) error { - err := repo.CanReadProject(ctx, claims, id) +// CanModifyChecklist determines if claims has the authority to modify the specified checklist by id. +func (repo *Repository) CanModifyChecklist(ctx context.Context, claims auth.Claims, id string) error { + err := repo.CanReadChecklist(ctx, claims, id) if err != nil { return err } - // Admin users can update projects they have access to. + // Admin users can update checklists they have access to. if !claims.HasRole(auth.RoleAdmin) { return errors.WithStack(ErrForbidden) } @@ -88,21 +88,21 @@ func applyClaimsSelect(ctx context.Context, claims auth.Claims, query *sqlbuilde return nil } -// projectMapColumns is the list of columns needed for find. -var projectMapColumns = "id,account_id,name,status,created_at,updated_at,archived_at" +// checklistMapColumns is the list of columns needed for find. +var checklistMapColumns = "id,account_id,name,status,created_at,updated_at,archived_at" -// selectQuery constructs a base select query for Project. +// selectQuery constructs a base select query for Checklist. func selectQuery() *sqlbuilder.SelectBuilder { query := sqlbuilder.NewSelectBuilder() - query.Select(projectMapColumns) - query.From(projectTableName) + query.Select(checklistMapColumns) + query.From(checklistTableName) return query } // findRequestQuery generates the select query for the given find request. // TODO: Need to figure out why can't parse the args when appending the where // to the query. -func findRequestQuery(req ProjectFindRequest) (*sqlbuilder.SelectBuilder, []interface{}) { +func findRequestQuery(req ChecklistFindRequest) (*sqlbuilder.SelectBuilder, []interface{}) { query := selectQuery() if req.Where != "" { @@ -124,19 +124,19 @@ func findRequestQuery(req ProjectFindRequest) (*sqlbuilder.SelectBuilder, []inte return query, req.Args } -// Find gets all the projects from the database based on the request params. -func (repo *Repository) Find(ctx context.Context, claims auth.Claims, req ProjectFindRequest) (Projects, error) { +// Find gets all the checklists from the database based on the request params. +func (repo *Repository) Find(ctx context.Context, claims auth.Claims, req ChecklistFindRequest) (Checklists, error) { query, args := findRequestQuery(req) return find(ctx, claims, repo.DbConn, query, args, req.IncludeArchived) } -// find internal method for getting all the projects from the database using a select query. -func find(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, query *sqlbuilder.SelectBuilder, args []interface{}, includedArchived bool) (Projects, error) { - span, ctx := tracer.StartSpanFromContext(ctx, "internal.project.Find") +// find internal method for getting all the checklists from the database using a select query. +func find(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, query *sqlbuilder.SelectBuilder, args []interface{}, includedArchived bool) (Checklists, error) { + span, ctx := tracer.StartSpanFromContext(ctx, "internal.checklist.Find") defer span.Finish() - query.Select(projectMapColumns) - query.From(projectTableName) + query.Select(checklistMapColumns) + query.From(checklistTableName) if !includedArchived { query.Where(query.IsNull("archived_at")) } @@ -154,15 +154,15 @@ func find(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, query *sqlbu rows, err := dbConn.QueryContext(ctx, queryStr, args...) if err != nil { err = errors.Wrapf(err, "query - %s", query.String()) - err = errors.WithMessage(err, "find projects failed") + err = errors.WithMessage(err, "find checklists failed") return nil, err } // Iterate over each row. - resp := []*Project{} + resp := []*Checklist{} for rows.Next() { var ( - m Project + m Checklist err error ) err = rows.Scan(&m.ID, &m.AccountID, &m.Name, &m.Status, &m.CreatedAt, &m.UpdatedAt, &m.ArchivedAt) @@ -177,17 +177,17 @@ func find(ctx context.Context, claims auth.Claims, dbConn *sqlx.DB, query *sqlbu return resp, nil } -// ReadByID gets the specified project by ID from the database. -func (repo *Repository) ReadByID(ctx context.Context, claims auth.Claims, id string) (*Project, error) { - return repo.Read(ctx, claims, ProjectReadRequest{ +// ReadByID gets the specified checklist by ID from the database. +func (repo *Repository) ReadByID(ctx context.Context, claims auth.Claims, id string) (*Checklist, error) { + return repo.Read(ctx, claims, ChecklistReadRequest{ ID: id, IncludeArchived: false, }) } -// Read gets the specified project from the database. -func (repo *Repository) Read(ctx context.Context, claims auth.Claims, req ProjectReadRequest) (*Project, error) { - span, ctx := tracer.StartSpanFromContext(ctx, "internal.project.Read") +// Read gets the specified checklist from the database. +func (repo *Repository) Read(ctx context.Context, claims auth.Claims, req ChecklistReadRequest) (*Checklist, error) { + span, ctx := tracer.StartSpanFromContext(ctx, "internal.checklist.Read") defer span.Finish() // Validate the request. @@ -205,7 +205,7 @@ func (repo *Repository) Read(ctx context.Context, claims auth.Claims, req Projec if err != nil { return nil, err } else if res == nil || len(res) == 0 { - err = errors.WithMessagef(ErrNotFound, "project %s not found", req.ID) + err = errors.WithMessagef(ErrNotFound, "checklist %s not found", req.ID) return nil, err } @@ -213,12 +213,12 @@ func (repo *Repository) Read(ctx context.Context, claims auth.Claims, req Projec return u, nil } -// Create inserts a new project into the database. -func (repo *Repository) Create(ctx context.Context, claims auth.Claims, req ProjectCreateRequest, now time.Time) (*Project, error) { - span, ctx := tracer.StartSpanFromContext(ctx, "internal.project.Create") +// Create inserts a new checklist into the database. +func (repo *Repository) Create(ctx context.Context, claims auth.Claims, req ChecklistCreateRequest, now time.Time) (*Checklist, error) { + span, ctx := tracer.StartSpanFromContext(ctx, "internal.checklist.Create") defer span.Finish() if claims.Audience != "" { - // Admin users can update projects they have access to. + // Admin users can update checklists they have access to. if !claims.HasRole(auth.RoleAdmin) { return nil, errors.WithStack(ErrForbidden) } @@ -253,11 +253,11 @@ func (repo *Repository) Create(ctx context.Context, claims auth.Claims, req Proj // Postgres truncates times to milliseconds when storing. We and do the same // here so the value we return is consistent with what we store. now = now.Truncate(time.Millisecond) - m := Project{ + m := Checklist{ ID: uuid.NewRandom().String(), AccountID: req.AccountID, Name: req.Name, - Status: ProjectStatus_Active, + Status: ChecklistStatus_Active, CreatedAt: now, UpdatedAt: now, } @@ -268,7 +268,7 @@ func (repo *Repository) Create(ctx context.Context, claims auth.Claims, req Proj // Build the insert SQL statement. query := sqlbuilder.NewInsertBuilder() - query.InsertInto(projectTableName) + query.InsertInto(checklistTableName) query.Cols( "id", "account_id", @@ -295,16 +295,16 @@ func (repo *Repository) Create(ctx context.Context, claims auth.Claims, req Proj _, err = repo.DbConn.ExecContext(ctx, sql, args...) if err != nil { err = errors.Wrapf(err, "query - %s", query.String()) - err = errors.WithMessage(err, "create project failed") + err = errors.WithMessage(err, "create checklist failed") return nil, err } return &m, nil } -// Update replaces an project in the database. -func (repo *Repository) Update(ctx context.Context, claims auth.Claims, req ProjectUpdateRequest, now time.Time) error { - span, ctx := tracer.StartSpanFromContext(ctx, "internal.project.Update") +// Update replaces an checklist in the database. +func (repo *Repository) Update(ctx context.Context, claims auth.Claims, req ChecklistUpdateRequest, now time.Time) error { + span, ctx := tracer.StartSpanFromContext(ctx, "internal.checklist.Update") defer span.Finish() // Validate the request. @@ -314,8 +314,8 @@ func (repo *Repository) Update(ctx context.Context, claims auth.Claims, req Proj return err } - // Ensure the claims can modify the project specified in the request. - err = repo.CanModifyProject(ctx, claims, req.ID) + // Ensure the claims can modify the checklist specified in the request. + err = repo.CanModifyChecklist(ctx, claims, req.ID) if err != nil { return err } @@ -332,7 +332,7 @@ func (repo *Repository) Update(ctx context.Context, claims auth.Claims, req Proj now = now.Truncate(time.Millisecond) // Build the update SQL statement. query := sqlbuilder.NewUpdateBuilder() - query.Update(projectTableName) + query.Update(checklistTableName) var fields []string if req.Name != nil { fields = append(fields, query.Assign("name", req.Name)) @@ -357,16 +357,16 @@ func (repo *Repository) Update(ctx context.Context, claims auth.Claims, req Proj _, err = repo.DbConn.ExecContext(ctx, sql, args...) if err != nil { err = errors.Wrapf(err, "query - %s", query.String()) - err = errors.WithMessagef(err, "update project %s failed", req.ID) + err = errors.WithMessagef(err, "update checklist %s failed", req.ID) return err } return nil } -// Archive soft deleted the project from the database. -func (repo *Repository) Archive(ctx context.Context, claims auth.Claims, req ProjectArchiveRequest, now time.Time) error { - span, ctx := tracer.StartSpanFromContext(ctx, "internal.project.Archive") +// Archive soft deleted the checklist from the database. +func (repo *Repository) Archive(ctx context.Context, claims auth.Claims, req ChecklistArchiveRequest, now time.Time) error { + span, ctx := tracer.StartSpanFromContext(ctx, "internal.checklist.Archive") defer span.Finish() // Validate the request. @@ -376,8 +376,8 @@ func (repo *Repository) Archive(ctx context.Context, claims auth.Claims, req Pro return err } - // Ensure the claims can modify the project specified in the request. - err = repo.CanModifyProject(ctx, claims, req.ID) + // Ensure the claims can modify the checklist specified in the request. + err = repo.CanModifyChecklist(ctx, claims, req.ID) if err != nil { return err } @@ -394,7 +394,7 @@ func (repo *Repository) Archive(ctx context.Context, claims auth.Claims, req Pro now = now.Truncate(time.Millisecond) // Build the update SQL statement. query := sqlbuilder.NewUpdateBuilder() - query.Update(projectTableName) + query.Update(checklistTableName) query.Set( query.Assign("archived_at", now), ) @@ -406,16 +406,16 @@ func (repo *Repository) Archive(ctx context.Context, claims auth.Claims, req Pro _, err = repo.DbConn.ExecContext(ctx, sql, args...) if err != nil { err = errors.Wrapf(err, "query - %s", query.String()) - err = errors.WithMessagef(err, "archive project %s failed", req.ID) + err = errors.WithMessagef(err, "archive checklist %s failed", req.ID) return err } return nil } -// Delete removes an project from the database. -func (repo *Repository) Delete(ctx context.Context, claims auth.Claims, req ProjectDeleteRequest) error { - span, ctx := tracer.StartSpanFromContext(ctx, "internal.project.Delete") +// Delete removes an checklist from the database. +func (repo *Repository) Delete(ctx context.Context, claims auth.Claims, req ChecklistDeleteRequest) error { + span, ctx := tracer.StartSpanFromContext(ctx, "internal.checklist.Delete") defer span.Finish() // Validate the request. @@ -425,15 +425,15 @@ func (repo *Repository) Delete(ctx context.Context, claims auth.Claims, req Proj return err } - // Ensure the claims can modify the project specified in the request. - err = repo.CanModifyProject(ctx, claims, req.ID) + // Ensure the claims can modify the checklist specified in the request. + err = repo.CanModifyChecklist(ctx, claims, req.ID) if err != nil { return err } // Build the delete SQL statement. query := sqlbuilder.NewDeleteBuilder() - query.DeleteFrom(projectTableName) + query.DeleteFrom(checklistTableName) query.Where(query.Equal("id", req.ID)) // Execute the query with the provided context. sql, args := query.Build() @@ -441,7 +441,7 @@ func (repo *Repository) Delete(ctx context.Context, claims auth.Claims, req Proj _, err = repo.DbConn.ExecContext(ctx, sql, args...) if err != nil { err = errors.Wrapf(err, "query - %s", query.String()) - err = errors.WithMessagef(err, "delete project %s failed", req.ID) + err = errors.WithMessagef(err, "delete checklist %s failed", req.ID) return err } diff --git a/internal/project/project_test.go b/internal/checklist/checklist_test.go similarity index 91% rename from internal/project/project_test.go rename to internal/checklist/checklist_test.go index 8e702aa..18b2b4a 100644 --- a/internal/project/project_test.go +++ b/internal/checklist/checklist_test.go @@ -1,4 +1,4 @@ -package project +package checklist import ( "os" @@ -37,7 +37,7 @@ func TestFindRequestQuery(t *testing.T) { offset uint = 34 ) - req := ProjectFindRequest{ + req := ChecklistFindRequest{ Where: "field1 = ? or field2 = ?", Args: []interface{}{ "lee brown", @@ -53,7 +53,7 @@ func TestFindRequestQuery(t *testing.T) { Offset: &offset, } - expected := "SELECT " + projectMapColumns + " FROM " + projectTableName + " WHERE (field1 = ? or field2 = ?) ORDER BY id asc, created_at desc LIMIT 12 OFFSET 34" + expected := "SELECT " + checklistMapColumns + " FROM " + checklistTableName + " WHERE (field1 = ? or field2 = ?) ORDER BY id asc, created_at desc LIMIT 12 OFFSET 34" res, args := findRequestQuery(req) if diff := cmp.Diff(res.String(), expected); diff != "" { t.Fatalf("\t%s\tExpected result query to match. Diff:\n%s", tests.Failed, diff) @@ -101,9 +101,6 @@ func TestApplyClaimsSelect(t *testing.T) { t.Logf("\t%s\tapplyClaimsSelect ok.", tests.Success) } - } - } - } diff --git a/internal/checklist/models.go b/internal/checklist/models.go new file mode 100644 index 0000000..1c6b5bd --- /dev/null +++ b/internal/checklist/models.go @@ -0,0 +1,184 @@ +package checklist + +import ( + "context" + "time" + + "database/sql/driver" + "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" + "github.com/jmoiron/sqlx" + "github.com/lib/pq" + "github.com/pkg/errors" + "gopkg.in/go-playground/validator.v9" +) + +// Repository defines the required dependencies for Checklist. +type Repository struct { + DbConn *sqlx.DB +} + +// NewRepository creates a new Repository that defines dependencies for Checklist. +func NewRepository(db *sqlx.DB) *Repository { + return &Repository{ + DbConn: db, + } +} + +// Checklist represents a workflow. +type Checklist struct { + ID string `json:"id" validate:"required,uuid" example:"985f1746-1d9f-459f-a2d9-fc53ece5ae86"` + AccountID string `json:"account_id" validate:"required,uuid" truss:"api-create"` + Name string `json:"name" validate:"required" example:"Rocket Launch"` + Status ChecklistStatus `json:"status" validate:"omitempty,oneof=active disabled" enums:"active,disabled" swaggertype:"string" example:"active"` + CreatedAt time.Time `json:"created_at" truss:"api-read"` + UpdatedAt time.Time `json:"updated_at" truss:"api-read"` + ArchivedAt *pq.NullTime `json:"archived_at,omitempty" truss:"api-hide"` +} + +// ChecklistResponse represents a workflow that is returned for display. +type ChecklistResponse struct { + ID string `json:"id" validate:"required,uuid" example:"985f1746-1d9f-459f-a2d9-fc53ece5ae86"` + AccountID string `json:"account_id" validate:"required,uuid" truss:"api-create" example:"c4653bf9-5978-48b7-89c5-95704aebb7e2"` + Name string `json:"name" validate:"required" example:"Rocket Launch"` + Status web.EnumResponse `json:"status"` // Status is enum with values [active, disabled]. + CreatedAt web.TimeResponse `json:"created_at"` // CreatedAt contains multiple format options for display. + UpdatedAt web.TimeResponse `json:"updated_at"` // UpdatedAt contains multiple format options for display. + ArchivedAt *web.TimeResponse `json:"archived_at,omitempty"` // ArchivedAt contains multiple format options for display. +} + +// Response transforms Checklist and ChecklistResponse that is used for display. +// Additional filtering by context values or translations could be applied. +func (m *Checklist) Response(ctx context.Context) *ChecklistResponse { + if m == nil { + return nil + } + + r := &ChecklistResponse{ + ID: m.ID, + AccountID: m.AccountID, + Name: m.Name, + Status: web.NewEnumResponse(ctx, m.Status, ChecklistStatus_ValuesInterface()...), + CreatedAt: web.NewTimeResponse(ctx, m.CreatedAt), + UpdatedAt: web.NewTimeResponse(ctx, m.UpdatedAt), + } + + if m.ArchivedAt != nil && !m.ArchivedAt.Time.IsZero() { + at := web.NewTimeResponse(ctx, m.ArchivedAt.Time) + r.ArchivedAt = &at + } + + return r +} + +// Checklists a list of Checklists. +type Checklists []*Checklist + +// Response transforms a list of Checklists to a list of ChecklistResponses. +func (m *Checklists) Response(ctx context.Context) []*ChecklistResponse { + var l []*ChecklistResponse + if m != nil && len(*m) > 0 { + for _, n := range *m { + l = append(l, n.Response(ctx)) + } + } + + return l +} + +// ChecklistCreateRequest contains information needed to create a new Checklist. +type ChecklistCreateRequest struct { + AccountID string `json:"account_id" validate:"required,uuid" example:"c4653bf9-5978-48b7-89c5-95704aebb7e2"` + Name string `json:"name" validate:"required" example:"Rocket Launch"` + Status *ChecklistStatus `json:"status,omitempty" validate:"omitempty,oneof=active disabled" enums:"active,disabled" swaggertype:"string" example:"active"` +} + +// ChecklistReadRequest defines the information needed to read a checklist. +type ChecklistReadRequest struct { + ID string `json:"id" validate:"required,uuid" example:"985f1746-1d9f-459f-a2d9-fc53ece5ae86"` + IncludeArchived bool `json:"include-archived" example:"false"` +} + +// ChecklistUpdateRequest defines what information may be provided to modify an existing +// Checklist. All fields are optional so clients can send just the fields they want +// changed. It uses pointer fields so we can differentiate between a field that +// was not provided and a field that was provided as explicitly blank. +type ChecklistUpdateRequest struct { + ID string `json:"id" validate:"required,uuid" example:"985f1746-1d9f-459f-a2d9-fc53ece5ae86"` + Name *string `json:"name,omitempty" validate:"omitempty" example:"Rocket Launch to Moon"` + Status *ChecklistStatus `json:"status,omitempty" validate:"omitempty,oneof=active disabled" enums:"active,disabled" swaggertype:"string" example:"disabled"` +} + +// ChecklistArchiveRequest defines the information needed to archive a checklist. This will archive (soft-delete) the +// existing database entry. +type ChecklistArchiveRequest struct { + ID string `json:"id" validate:"required,uuid" example:"985f1746-1d9f-459f-a2d9-fc53ece5ae86"` +} + +// ChecklistDeleteRequest defines the information needed to delete a checklist. +type ChecklistDeleteRequest struct { + ID string `json:"id" validate:"required,uuid" example:"985f1746-1d9f-459f-a2d9-fc53ece5ae86"` +} + +// ChecklistFindRequest defines the possible options to search for checklists. By default +// archived checklist will be excluded from response. +type ChecklistFindRequest struct { + Where string `json:"where" example:"name = ? and status = ?"` + Args []interface{} `json:"args" swaggertype:"array,string" example:"Moon Launch,active"` + Order []string `json:"order" example:"created_at desc"` + Limit *uint `json:"limit" example:"10"` + Offset *uint `json:"offset" example:"20"` + IncludeArchived bool `json:"include-archived" example:"false"` +} + +// ChecklistStatus represents the status of checklist. +type ChecklistStatus string + +// ChecklistStatus values define the status field of checklist. +const ( + // ChecklistStatus_Active defines the status of active for checklist. + ChecklistStatus_Active ChecklistStatus = "active" + // ChecklistStatus_Disabled defines the status of disabled for checklist. + ChecklistStatus_Disabled ChecklistStatus = "disabled" +) + +// ChecklistStatus_Values provides list of valid ChecklistStatus values. +var ChecklistStatus_Values = []ChecklistStatus{ + ChecklistStatus_Active, + ChecklistStatus_Disabled, +} + +// ChecklistStatus_ValuesInterface returns the ChecklistStatus options as a slice interface. +func ChecklistStatus_ValuesInterface() []interface{} { + var l []interface{} + for _, v := range ChecklistStatus_Values { + l = append(l, v.String()) + } + return l +} + +// Scan supports reading the ChecklistStatus value from the database. +func (s *ChecklistStatus) Scan(value interface{}) error { + asBytes, ok := value.([]byte) + if !ok { + return errors.New("Scan source is not []byte") + } + + *s = ChecklistStatus(string(asBytes)) + return nil +} + +// Value converts the ChecklistStatus value to be stored in the database. +func (s ChecklistStatus) Value() (driver.Value, error) { + v := validator.New() + errs := v.Var(s, "required,oneof=active disabled") + if errs != nil { + return nil, errs + } + + return string(s), nil +} + +// String converts the ChecklistStatus value to a string. +func (s ChecklistStatus) String() string { + return string(s) +} diff --git a/internal/platform/img-resize/img_resize.go b/internal/platform/img-resize/img_resize.go index e9d9a12..7eb45a0 100644 --- a/internal/platform/img-resize/img_resize.go +++ b/internal/platform/img-resize/img_resize.go @@ -177,7 +177,7 @@ func S3ImgSrc(ctx context.Context, redisClient *redistrace.Client, s3UrlFormatte Prefix: aws.String(s3Path), }) if err != nil { - return defaultSrc, errors.WithStack(err) + return defaultSrc, errors.WithMessagef(err, "Failed to list objects for s3://%s/%s", s3Bucket, s3Path) } // Loop through all the S3 objects and store by in map by diff --git a/internal/platform/web/template-renderer/README.md b/internal/platform/web/tmplrender/README.md similarity index 100% rename from internal/platform/web/template-renderer/README.md rename to internal/platform/web/tmplrender/README.md diff --git a/internal/platform/web/template-renderer/template_renderer.go b/internal/platform/web/tmplrender/tmplrender.go similarity index 99% rename from internal/platform/web/template-renderer/template_renderer.go rename to internal/platform/web/tmplrender/tmplrender.go index 5321a66..d461064 100644 --- a/internal/platform/web/template-renderer/template_renderer.go +++ b/internal/platform/web/tmplrender/tmplrender.go @@ -1,4 +1,4 @@ -package template_renderer +package tmplrender import ( "context" diff --git a/internal/project/models.go b/internal/project/models.go deleted file mode 100644 index eff7cfb..0000000 --- a/internal/project/models.go +++ /dev/null @@ -1,184 +0,0 @@ -package project - -import ( - "context" - "time" - - "database/sql/driver" - "geeks-accelerator/oss/saas-starter-kit/internal/platform/web" - "github.com/jmoiron/sqlx" - "github.com/lib/pq" - "github.com/pkg/errors" - "gopkg.in/go-playground/validator.v9" -) - -// Repository defines the required dependencies for Project. -type Repository struct { - DbConn *sqlx.DB -} - -// NewRepository creates a new Repository that defines dependencies for Project. -func NewRepository(db *sqlx.DB) *Repository { - return &Repository{ - DbConn: db, - } -} - -// Project represents a workflow. -type Project struct { - ID string `json:"id" validate:"required,uuid" example:"985f1746-1d9f-459f-a2d9-fc53ece5ae86"` - AccountID string `json:"account_id" validate:"required,uuid" truss:"api-create"` - Name string `json:"name" validate:"required" example:"Rocket Launch"` - Status ProjectStatus `json:"status" validate:"omitempty,oneof=active disabled" enums:"active,disabled" swaggertype:"string" example:"active"` - CreatedAt time.Time `json:"created_at" truss:"api-read"` - UpdatedAt time.Time `json:"updated_at" truss:"api-read"` - ArchivedAt *pq.NullTime `json:"archived_at,omitempty" truss:"api-hide"` -} - -// ProjectResponse represents a workflow that is returned for display. -type ProjectResponse struct { - ID string `json:"id" validate:"required,uuid" example:"985f1746-1d9f-459f-a2d9-fc53ece5ae86"` - AccountID string `json:"account_id" validate:"required,uuid" truss:"api-create" example:"c4653bf9-5978-48b7-89c5-95704aebb7e2"` - Name string `json:"name" validate:"required" example:"Rocket Launch"` - Status web.EnumResponse `json:"status"` // Status is enum with values [active, disabled]. - CreatedAt web.TimeResponse `json:"created_at"` // CreatedAt contains multiple format options for display. - UpdatedAt web.TimeResponse `json:"updated_at"` // UpdatedAt contains multiple format options for display. - ArchivedAt *web.TimeResponse `json:"archived_at,omitempty"` // ArchivedAt contains multiple format options for display. -} - -// Response transforms Project and ProjectResponse that is used for display. -// Additional filtering by context values or translations could be applied. -func (m *Project) Response(ctx context.Context) *ProjectResponse { - if m == nil { - return nil - } - - r := &ProjectResponse{ - ID: m.ID, - AccountID: m.AccountID, - Name: m.Name, - Status: web.NewEnumResponse(ctx, m.Status, ProjectStatus_ValuesInterface()...), - CreatedAt: web.NewTimeResponse(ctx, m.CreatedAt), - UpdatedAt: web.NewTimeResponse(ctx, m.UpdatedAt), - } - - if m.ArchivedAt != nil && !m.ArchivedAt.Time.IsZero() { - at := web.NewTimeResponse(ctx, m.ArchivedAt.Time) - r.ArchivedAt = &at - } - - return r -} - -// Projects a list of Projects. -type Projects []*Project - -// Response transforms a list of Projects to a list of ProjectResponses. -func (m *Projects) Response(ctx context.Context) []*ProjectResponse { - var l []*ProjectResponse - if m != nil && len(*m) > 0 { - for _, n := range *m { - l = append(l, n.Response(ctx)) - } - } - - return l -} - -// ProjectCreateRequest contains information needed to create a new Project. -type ProjectCreateRequest struct { - AccountID string `json:"account_id" validate:"required,uuid" example:"c4653bf9-5978-48b7-89c5-95704aebb7e2"` - Name string `json:"name" validate:"required" example:"Rocket Launch"` - Status *ProjectStatus `json:"status,omitempty" validate:"omitempty,oneof=active disabled" enums:"active,disabled" swaggertype:"string" example:"active"` -} - -// ProjectReadRequest defines the information needed to read a project. -type ProjectReadRequest struct { - ID string `json:"id" validate:"required,uuid" example:"985f1746-1d9f-459f-a2d9-fc53ece5ae86"` - IncludeArchived bool `json:"include-archived" example:"false"` -} - -// ProjectUpdateRequest defines what information may be provided to modify an existing -// Project. All fields are optional so clients can send just the fields they want -// changed. It uses pointer fields so we can differentiate between a field that -// was not provided and a field that was provided as explicitly blank. -type ProjectUpdateRequest struct { - ID string `json:"id" validate:"required,uuid" example:"985f1746-1d9f-459f-a2d9-fc53ece5ae86"` - Name *string `json:"name,omitempty" validate:"omitempty" example:"Rocket Launch to Moon"` - Status *ProjectStatus `json:"status,omitempty" validate:"omitempty,oneof=active disabled" enums:"active,disabled" swaggertype:"string" example:"disabled"` -} - -// ProjectArchiveRequest defines the information needed to archive a project. This will archive (soft-delete) the -// existing database entry. -type ProjectArchiveRequest struct { - ID string `json:"id" validate:"required,uuid" example:"985f1746-1d9f-459f-a2d9-fc53ece5ae86"` -} - -// ProjectDeleteRequest defines the information needed to delete a project. -type ProjectDeleteRequest struct { - ID string `json:"id" validate:"required,uuid" example:"985f1746-1d9f-459f-a2d9-fc53ece5ae86"` -} - -// ProjectFindRequest defines the possible options to search for projects. By default -// archived project will be excluded from response. -type ProjectFindRequest struct { - Where string `json:"where" example:"name = ? and status = ?"` - Args []interface{} `json:"args" swaggertype:"array,string" example:"Moon Launch,active"` - Order []string `json:"order" example:"created_at desc"` - Limit *uint `json:"limit" example:"10"` - Offset *uint `json:"offset" example:"20"` - IncludeArchived bool `json:"include-archived" example:"false"` -} - -// ProjectStatus represents the status of project. -type ProjectStatus string - -// ProjectStatus values define the status field of project. -const ( - // ProjectStatus_Active defines the status of active for project. - ProjectStatus_Active ProjectStatus = "active" - // ProjectStatus_Disabled defines the status of disabled for project. - ProjectStatus_Disabled ProjectStatus = "disabled" -) - -// ProjectStatus_Values provides list of valid ProjectStatus values. -var ProjectStatus_Values = []ProjectStatus{ - ProjectStatus_Active, - ProjectStatus_Disabled, -} - -// ProjectStatus_ValuesInterface returns the ProjectStatus options as a slice interface. -func ProjectStatus_ValuesInterface() []interface{} { - var l []interface{} - for _, v := range ProjectStatus_Values { - l = append(l, v.String()) - } - return l -} - -// Scan supports reading the ProjectStatus value from the database. -func (s *ProjectStatus) Scan(value interface{}) error { - asBytes, ok := value.([]byte) - if !ok { - return errors.New("Scan source is not []byte") - } - - *s = ProjectStatus(string(asBytes)) - return nil -} - -// Value converts the ProjectStatus value to be stored in the database. -func (s ProjectStatus) Value() (driver.Value, error) { - v := validator.New() - errs := v.Var(s, "required,oneof=active disabled") - if errs != nil { - return nil, errs - } - - return string(s), nil -} - -// String converts the ProjectStatus value to a string. -func (s ProjectStatus) String() string { - return string(s) -} diff --git a/internal/schema/migrations.go b/internal/schema/migrations.go index 536002a..4b92321 100644 --- a/internal/schema/migrations.go +++ b/internal/schema/migrations.go @@ -662,6 +662,21 @@ func migrationList(ctx context.Context, db *sqlx.DB, log *log.Logger, isUnittest return nil }, }, + // Remove default value for users.timezone. + { + ID: "20200118-01", + Migrate: func(tx *sql.Tx) error { + q1 := `ALTER TABLE projects RENAME TO checklists` + if _, err := tx.Exec(q1); err != nil { + return errors.Wrapf(err, "Query failed %s", q1) + } + + return nil + }, + Rollback: func(tx *sql.Tx) error { + return nil + }, + }, } } diff --git a/internal/project_route/project_routes.go b/internal/webroute/webroute.go similarity index 70% rename from internal/project_route/project_routes.go rename to internal/webroute/webroute.go index 6114cfc..6e05491 100644 --- a/internal/project_route/project_routes.go +++ b/internal/webroute/webroute.go @@ -1,21 +1,22 @@ -package project_route +package webroute import ( "fmt" - "github.com/pkg/errors" "net" "net/url" "os" "strings" + + "github.com/pkg/errors" ) -type ProjectRoute struct { +type WebRoute struct { webAppUrl url.URL webApiUrl url.URL } -func New(apiBaseUrl, appBaseUrl string) (ProjectRoute, error) { - var r ProjectRoute +func New(apiBaseUrl, appBaseUrl string) (WebRoute, error) { + var r WebRoute apiUrl, err := url.Parse(apiBaseUrl) if err != nil { @@ -32,37 +33,37 @@ func New(apiBaseUrl, appBaseUrl string) (ProjectRoute, error) { return r, nil } -func (r ProjectRoute) WebAppUrl(urlPath string) string { +func (r WebRoute) WebAppUrl(urlPath string) string { u := r.webAppUrl u.Path = urlPath return u.String() } -func (r ProjectRoute) WebApiUrl(urlPath string) string { +func (r WebRoute) WebApiUrl(urlPath string) string { u := r.webApiUrl u.Path = urlPath return u.String() } -func (r ProjectRoute) UserResetPassword(resetHash string) string { +func (r WebRoute) UserResetPassword(resetHash string) string { u := r.webAppUrl u.Path = "/user/reset-password/" + resetHash return u.String() } -func (r ProjectRoute) UserInviteAccept(inviteHash string) string { +func (r WebRoute) UserInviteAccept(inviteHash string) string { u := r.webAppUrl u.Path = "/users/invite/" + inviteHash return u.String() } -func (r ProjectRoute) ApiDocs() string { +func (r WebRoute) ApiDocs() string { u := r.webApiUrl u.Path = "/docs" return u.String() } -func (r ProjectRoute) ApiDocsJson(internal bool) string { +func (r WebRoute) ApiDocsJson(internal bool) string { u := r.webApiUrl if ev := os.Getenv("USE_NETWORK_ALIAS"); ev != "" {