You've already forked golang-saas-starter-kit
mirror of
https://github.com/raseels-repos/golang-saas-starter-kit.git
synced 2025-08-08 22:36:41 +02:00
issue #28 - renamed project to checklist
This commit is contained in:
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
184
internal/checklist/models.go
Normal file
184
internal/checklist/models.go
Normal file
@ -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)
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
@ -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
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,13 +10,13 @@ import (
|
||||
"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 {
|
||||
@ -33,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 != "" {
|
||||
|
Reference in New Issue
Block a user