Skip to content

Commit

Permalink
commit 27
Browse files Browse the repository at this point in the history
  • Loading branch information
ShawnSPG committed Dec 17, 2023
1 parent 92fea3d commit c5c788d
Show file tree
Hide file tree
Showing 198 changed files with 41,468 additions and 6 deletions.
44 changes: 44 additions & 0 deletions business/core/product/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package product

import (
"fmt"

"github.com/google/uuid"
"github.com/shawnzxx/service/business/sys/validate"
)

// QueryFilter holds the available fields a query can be filtered on.
type QueryFilter struct {
ID *uuid.UUID `validate:"omitempty"`
Name *string `validate:"omitempty,min=3"`
Cost *float64 `validate:"omitempty,numeric"`
Quantity *int `validate:"omitempty,numeric"`
}

// Validate checks the data in the model is considered clean.
func (qf *QueryFilter) Validate() error {
if err := validate.Check(qf); err != nil {
return fmt.Errorf("validate: %w", err)
}
return nil
}

// WithProductID sets the ID field of the QueryFilter value.
func (qf *QueryFilter) WithProductID(productID uuid.UUID) {
qf.ID = &productID
}

// WithName sets the Name field of the QueryFilter value.
func (qf *QueryFilter) WithName(name string) {
qf.Name = &name
}

// WithCost sets the Cost field of the QueryFilter value.
func (qf *QueryFilter) WithCost(cost float64) {
qf.Cost = &cost
}

// WithQuantity sets the Quantity field of the QueryFilter value.
func (qf *QueryFilter) WithQuantity(quantity int) {
qf.Quantity = &quantity
}
40 changes: 40 additions & 0 deletions business/core/product/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package product

import (
"time"

"github.com/google/uuid"
)

// Product represents an individual product.
type Product struct {
ID uuid.UUID
Name string
Cost float64
Quantity int
Sold int // this is aggregation other table data should not in here
Revenue int // this is aggregation other table data should not in here
UserID uuid.UUID // product have user table foreign key, user don't have product table foreign key, avoid cycle dependency
DateCreated time.Time
DateUpdated time.Time
}

// NewProduct is what we require from clients when adding a Product.
type NewProduct struct {
Name string
Cost float64
Quantity int
UserID uuid.UUID
}

// UpdateProduct defines what information may be provided to modify an
// existing Product. 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. Normally we do not want to use pointers to basic types but
// we make exceptions around marshalling/unmarshalling.
type UpdateProduct struct {
Name *string
Cost *float64
Quantity *int
}
20 changes: 20 additions & 0 deletions business/core/product/order.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package product

import (
"github.com/shawnzxx/service/business/data/order"
)

// DefaultOrderBy represents the default way we sort.
var DefaultOrderBy = order.NewBy(OrderByProdID, order.ASC)

// Set of fields that the results can be ordered by. These are the names
// that should be used by the application layer.
const (
OrderByProdID = "productid"
OrderByName = "name"
OrderByCost = "cost"
OrderByQuantity = "quantity"
OrderBySold = "sold"
OrderByRevenue = "revenue"
OrderByUserID = "userid"
)
140 changes: 140 additions & 0 deletions business/core/product/product.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Package product provides an example of a core business API. Right now these
// calls are just wrapping the data/store layer. But at some point you will
// want audit or something that isn't specific to the data/store layer.
package product

import (
"context"
"errors"
"fmt"
"github.com/shawnzxx/service/business/data/order"
"time"

"github.com/google/uuid"
"github.com/shawnzxx/service/business/core/user"
"go.uber.org/zap"
)

// ErrNotFound Set of error variables for CRUD operations.
var (
ErrNotFound = errors.New("product not found")
)

// Storer interface declares the behavior this package needs to perists and
// retrieve data.
type Storer interface {
Create(ctx context.Context, prd Product) error
Update(ctx context.Context, prd Product) error
Delete(ctx context.Context, prd Product) error
Query(ctx context.Context, filter QueryFilter, orderBy order.By, pageNumber int, rowsPerPage int) ([]Product, error)
Count(ctx context.Context, filter QueryFilter) (int, error)
QueryByID(ctx context.Context, productID uuid.UUID) (Product, error)
QueryByUserID(ctx context.Context, userID uuid.UUID) ([]Product, error)
}

// Core manages the set of APIs for product access.
type Core struct {
log *zap.SugaredLogger
usrCore *user.Core
storer Storer
}

// NewCore constructs a core for product api access.
// since product embedded user.ID, so we can inject usrCore into product.NewCore
// we didn't use it, but we just to show philosophy of inject usr domain into product.NewCore
func NewCore(log *zap.SugaredLogger, usrCore *user.Core, storer Storer) *Core {
core := Core{
log: log,
usrCore: usrCore,
storer: storer,
}

return &core
}

// Create adds a Product to the database. It returns the created Product with
// fields like ID and DateCreated populated.
func (c *Core) Create(ctx context.Context, np NewProduct) (Product, error) {
now := time.Now()

prd := Product{
ID: uuid.New(),
Name: np.Name,
Cost: np.Cost,
Quantity: np.Quantity,
UserID: np.UserID,
DateCreated: now,
DateUpdated: now,
}

if err := c.storer.Create(ctx, prd); err != nil {
return Product{}, fmt.Errorf("create: %w", err)
}

return prd, nil
}

// Update modifies data about a Product. It will error if the specified ID is
// invalid or does not reference an existing Product.
func (c *Core) Update(ctx context.Context, prd Product, up UpdateProduct) (Product, error) {
if up.Name != nil {
prd.Name = *up.Name
}
if up.Cost != nil {
prd.Cost = *up.Cost
}
if up.Quantity != nil {
prd.Quantity = *up.Quantity
}
prd.DateUpdated = time.Now()

if err := c.storer.Update(ctx, prd); err != nil {
return Product{}, fmt.Errorf("update: %w", err)
}

return prd, nil
}

// Delete removes the product identified by a given ID.
func (c *Core) Delete(ctx context.Context, prd Product) error {
if err := c.storer.Delete(ctx, prd); err != nil {
return fmt.Errorf("delete: %w", err)
}

return nil
}

// Query gets all Products from the database.
func (c *Core) Query(ctx context.Context, filter QueryFilter, orderBy order.By, pageNumber int, rowsPerPage int) ([]Product, error) {
prds, err := c.storer.Query(ctx, filter, orderBy, pageNumber, rowsPerPage)
if err != nil {
return nil, fmt.Errorf("query: %w", err)
}

return prds, nil
}

// Count returns the total number of products in the store.
func (c *Core) Count(ctx context.Context, filter QueryFilter) (int, error) {
return c.storer.Count(ctx, filter)
}

// QueryByID finds the product identified by a given ID.
func (c *Core) QueryByID(ctx context.Context, productID uuid.UUID) (Product, error) {
prd, err := c.storer.QueryByID(ctx, productID)
if err != nil {
return Product{}, fmt.Errorf("query: productID[%s]: %w", productID, err)
}

return prd, nil
}

// QueryByUserID finds the products identified by a given User ID.
func (c *Core) QueryByUserID(ctx context.Context, userID uuid.UUID) ([]Product, error) {
prds, err := c.storer.QueryByUserID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("query: %w", err)
}

return prds, nil
}
54 changes: 54 additions & 0 deletions business/core/user/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package user

import (
"fmt"
"net/mail"
"time"

"github.com/google/uuid"
"github.com/shawnzxx/service/business/sys/validate"
)

// QueryFilter holds the available fields a query can be filtered on.
type QueryFilter struct {
ID *uuid.UUID `validate:"omitempty"`
Name *string `validate:"omitempty,min=3"`
Email *mail.Address `validate:"omitempty"`
StartCreatedDate *time.Time `validate:"omitempty"`
EndCreatedDate *time.Time `validate:"omitempty"`
}

// Validate checks the data in the model is considered clean.
func (qf *QueryFilter) Validate() error {
if err := validate.Check(qf); err != nil {
return fmt.Errorf("validate: %w", err)
}
return nil
}

// WithUserID sets the ID field of the QueryFilter value.
func (qf *QueryFilter) WithUserID(userID uuid.UUID) {
qf.ID = &userID
}

// WithName sets the Name field of the QueryFilter value.
func (qf *QueryFilter) WithName(name string) {
qf.Name = &name
}

// WithEmail sets the Email field of the QueryFilter value.
func (qf *QueryFilter) WithEmail(email mail.Address) {
qf.Email = &email
}

// WithStartDateCreated sets the DateCreated field of the QueryFilter value.
func (qf *QueryFilter) WithStartDateCreated(startDate time.Time) {
d := startDate.UTC()
qf.StartCreatedDate = &d
}

// WithEndCreatedDate sets the DateCreated field of the QueryFilter value.
func (qf *QueryFilter) WithEndCreatedDate(endDate time.Time) {
d := endDate.UTC()
qf.EndCreatedDate = &d
}
15 changes: 9 additions & 6 deletions business/core/user/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import (
)

// User represents information about an individual user.
// here each filed we try to use type instead of string as much as possible
// so that upper layer need to use parse to covert to the type filed which help to do the validation.
// for each filed we try to use strong type instead of normal string, int
// upper layer need to use strong type to parse
// which help to do the data validation before pass to business layer.
type User struct {
ID uuid.UUID
Name string
Expand All @@ -23,7 +24,6 @@ type User struct {
}

// NewUser contains information needed to create a new user.
// for CRUD we need to create separate struct for each operation instead of re-use same struct
type NewUser struct {
Name string
Email mail.Address
Expand All @@ -33,9 +33,12 @@ type NewUser struct {
PasswordConfirm string
}

// UpdateUser contains information needed to update a user.
// we use pointer semantic let upper layer decide what are fields needs to update
// instead of provide the whole struct, because of pointer so if not provide will be nil
// UpdateUser defines what information may be provided to modify an
// existing User. 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. Normally we do not want to use pointers to basic types but
// we make exceptions around marshalling/unmarshalling.
type UpdateUser struct {
Name *string
Email *mail.Address
Expand Down
18 changes: 18 additions & 0 deletions business/core/user/order.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package user

import (
"github.com/shawnzxx/service/business/data/order"
)

// DefaultOrderBy represents the default way we sort.
var DefaultOrderBy = order.NewBy(OrderByID, order.ASC)

// Set of fields that the results can be ordered by. These are the names
// that should be used by the application layer.
const (
OrderByID = "userid"
OrderByName = "name"
OrderByEmail = "email"
OrderByRoles = "roles"
OrderByEnabled = "enabled"
)
1 change: 1 addition & 0 deletions business/core/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"errors"
"fmt"
"github.com/shawnzxx/service/business/data/order"
"net/mail"
"time"

Expand Down
32 changes: 32 additions & 0 deletions business/cview/usersummary/filter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package usersummary

import (
"fmt"

"github.com/google/uuid"
"github.com/shawnzxx/service/business/sys/validate"
)

// QueryFilter holds the available fields a query can be filtered on.
type QueryFilter struct {
UserID *uuid.UUID `validate:"omitempty,uuid4"`
UserName *string `validate:"omitempty,min=3"`
}

// Validate checks the data in the model is considered clean.
func (qf *QueryFilter) Validate() error {
if err := validate.Check(qf); err != nil {
return fmt.Errorf("validate: %w", err)
}
return nil
}

// WithUserID sets the ID field of the QueryFilter value.
func (qf *QueryFilter) WithUserID(userID uuid.UUID) {
qf.UserID = &userID
}

// WithUserName sets the UserName field of the QueryFilter value.
func (qf *QueryFilter) WithUserName(userName string) {
qf.UserName = &userName
}
Loading

0 comments on commit c5c788d

Please sign in to comment.