1
0
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:
Lee Brown
2020-01-18 15:36:16 -09:00
parent 0027bef370
commit 35242354d5
26 changed files with 632 additions and 618 deletions

View File

@ -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
}

View File

@ -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)
}
}
}
}

View 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)
}

View File

@ -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

View File

@ -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)
}

View File

@ -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
},
},
}
}

View File

@ -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 != "" {