-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
198 changed files
with
41,468 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ import ( | |
"context" | ||
"errors" | ||
"fmt" | ||
"github.com/shawnzxx/service/business/data/order" | ||
"net/mail" | ||
"time" | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.