kaimono

package module
v0.0.0-...-710224c Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 22, 2024 License: BSD-3-Clause Imports: 7 Imported by: 0

README

CI status License Go Report Card

kaimono

kaimono is a shopping cart library, that can be integrated into an existing server or as a standalone microservice.

It is a proof-of-concept implementation and not currently used in production (you might want to reach for the battle-tested medusa project at github.com/medusajs/medusa).

The word itself means "shopping" in japanese.

NOTE: currently in development/work-in-progress and not officially released.

Roadmap

  • Finish Readme / Documenting
  • Implement standard routes
  • Implement Admin routes
  • Extensive tests
  • Write some example code

Usage

Assumptions

The library makes only a few assumptions about the library consumer's backend:

  • each user, logged in or anonymous, will have an associated session token.
  • carts can be mapped to sessions.
  • when sessions for logged-in users expire, Carts will be migrated to new sessions.
  • when a user with an existing, valid session initiates a new session (e.g: different device), their existing Cart will be copied to the session (i.e: 1 cart to N sessions).
  • each product has associated with it an ID, a price, a title, a description.
Core types
Cart, CartItem, Discount

These are the main data types pass to and from the API.

Their definitions are:

type Cart struct {
	ID        string     `json:"id"`
	Items     []CartItem `json:"items"`
	Discounts []Discount `json:"discounts"`
}

type CartItem struct {
	ID        string     `json:"id"`
	Quantity  int        `json:"quantity"`
	Discounts []Discount `json:"discounts"`
	Price     Price      `json:"price"`
}

type Price struct {
	Currency string  `json:"currency"`
	Value    float64 `json:"value"`
}

type DiscountType string

const (
	PercentageDiscount  DiscountType = "percentage"
	FixedAmountDiscount DiscountType = "fixed-amount"
)

type Discount struct {
	ID    string       `json:"id"`
	Type  DiscountType `json:"type"`
	Value float64      `json:"value"`
}
Service

The Service type is the main type used to interact with the library.

It is backed by three interfaces: a DB interface, an Authorizer interface and a UserContextFetcher interface. These will be explained later in more detail, but the gist is that the DB interface provides CRUD methods for storage backend of the Cart while the UserContextFetcher allows the library to specify how the session token should be extracted from the request object. The Authorizer comes into play with the admin routes, authorizing (or not) a user for a given operation.

Service exposes two methods for every CRUD operation: one only acts within the scope of the request's associated user/session while the other skips checking the session and acts direclty on the cart specified by the ID.

The idea is that one set of methods is used to expose standard shopping cart functionality to a website, while the other is used for admin purposes.

Standard Routes

Services exposes a router function for getting the standard route router:

standardRouter := svc.Router("/cart")

The responses have the format:

{
    "data": { /* depends on endpoint */ },
    "error": "<check depending on status code>"
}

Check the documentation at: pkg.go.dev/github.com/aalbacetef/kaimono for full details of usage.

Admin Routes

Services exposes a router function for getting the admin route router:

adminRouter := svc.AdminRouter("/cart")

The responses have the format:

{
    "data": { /* depends on endpoint */ },
    "error": "<check depending on status code>"
}

Check the documentation at: pkg.go.dev/github.com/aalbacetef/kaimono for full details of usage.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrCartNotFound    = errors.New("cart not found")
	ErrSessionNotFound = errors.New("session not found")
	ErrAlreadyExists   = errors.New("already exists")
	ErrInvalidID       = errors.New("invalid ID")
)

Functions

This section is empty.

Types

type Authorizer

type Authorizer interface {

	// AuthorizeUser will determine if the user (retrieved from the request)
	// can perform the given operation on the specified resource.
	AuthorizeUser(req *http.Request, op Operation, resourceID string) error
}

type Cart

type Cart struct {
	ID        string     `json:"id"`
	Items     []CartItem `json:"items"`
	Discounts []Discount `json:"discounts"`
}

type CartItem

type CartItem struct {
	ID        string     `json:"id"`
	Quantity  int        `json:"quantity"`
	Discounts []Discount `json:"discounts"`
	Price     Price      `json:"price"`
}

type CreateCartResponse

type CreateCartResponse = Response[Cart]

type DB

type DB interface {
	// CreateCartForSession will instantiate a brand new empty Cart for the session.
	//
	// If no matching session is found it will return ErrSessionNotFound.
	// If a Cart already exists for that session, it will return ErrAlreadyExists.
	CreateCartForSession(sessionToken string) (Cart, error)

	// CreateCart will create a Cart without assigning it to a session.
	CreateCart() (Cart, error)

	// DeleteCart will delete the Cart matching the ID. It doesn't check
	// for permissions and should only be called after user has been authorized.
	//
	// If no Cart could be found, it will return ErrCartNotFound.
	DeleteCart(cartID string) error

	// UpdateCart will update the cart matching the cart.ID field. It doesn't check
	// for permissions and should only be called after user has been authorized.
	//
	// If no Cart could be found, it will return ErrCartNotFound.
	UpdateCart(cart Cart) error

	// LookupCart will find the Cart matching the ID. It doesn't check
	// for permissions and should only be called after user has been authorized.
	//
	// If no cart could be found, it will return ErrCartNotFound.
	LookupCart(cartID string) (Cart, error)

	// LookupCart will find the Cart for this session.
	//
	// If no matching session is found, it will return ErrSessionNotFound.
	// If no cart could be found, it will return ErrCartNotFound.
	LookupCartForSession(sessionToken string) (Cart, error)

	// AssignCartToSession will assign the cart specified by ID to the given
	// session.
	//
	// If no matching session is found, it will return ErrSessionNotFound.
	// If no cart could be found, it will return ErrCartNotFound.
	AssignCartToSession(cartID, sessionToken string) error
}

type Discount

type Discount struct {
	ID    string       `json:"id"`
	Type  DiscountType `json:"type"`
	Value float64      `json:"value"`
}

type DiscountType

type DiscountType string
const (
	PercentageDiscount  DiscountType = "percentage"
	FixedAmountDiscount DiscountType = "fixed-amount"
)

type ErrorResponse

type ErrorResponse struct {
	Data  any    `json:"data"`
	Error string `json:"error"`
}

type GetCartByIDResponse

type GetCartByIDResponse = Response[Cart]

type GetCartResponse

type GetCartResponse = Response[Cart]

type NotAuthorizedError

type NotAuthorizedError struct {
	Operation Operation `json:"operation"`
	ID        string    `json:"id"`
}

func (NotAuthorizedError) Error

func (e NotAuthorizedError) Error() string

type Operation

type Operation struct {
	Resource string        `json:"resource"`
	Type     OperationType `json:"type"`
}

type OperationType

type OperationType string
const (
	CreateOp OperationType = "create"
	ReadOp   OperationType = "read"
	UpdateOp OperationType = "update"
	DeleteOp OperationType = "delete"
)

type Price

type Price struct {
	Currency string  `json:"currency"`
	Value    float64 `json:"value"`
}

type Request

type Request[T any] struct {
	Data T `json:"data"`
}

type Response

type Response[T any] struct {
	Data  T      `json:"data"`
	Error string `json:"error"`
}

type Service

type Service struct {
	// contains filtered or unexported fields
}

func NewService

func NewService(db DB, usrCtxFetcher UserContextFetcher, authorizer Authorizer, logger *slog.Logger) (*Service, error)

func (*Service) AdminRouter

func (svc *Service) AdminRouter(base string) *chi.Mux

func (*Service) Create

func (svc *Service) Create(w http.ResponseWriter, req *http.Request)

Create will create a new Cart for the current session.

Status codes:

  • 201: Created successfully
  • 400: No session found for request
  • 409: Cart already exists
  • 500: unexpected error

func (*Service) CreateWithoutSession

func (svc *Service) CreateWithoutSession(w http.ResponseWriter, req *http.Request)

CreateWithoutSession will create an empty Cart without assigning it to a session.

Errors:

  • NotAuthorizedError if user is not authorized
  • ErrCartNotFound if cart is not found

Status Codes:

  • 200: OK
  • 403: Forbidden
  • 404: Cart not found

func (*Service) Delete

func (svc *Service) Delete(w http.ResponseWriter, req *http.Request)

Delete will delete the Cart for the current session. It will reject the Cart if the ID suplied does not match the expected one.

Status codes:

  • 204: Deleted successfully
  • 400: No session found for request
  • 404: No cart found for this session
  • 500: unexpected error

func (*Service) DeleteWithID

func (svc *Service) DeleteWithID(w http.ResponseWriter, req *http.Request)

Delete will delete the Cart with the supploed ID.

Status codes:

  • 204: Deleted successfully
  • 400: No session found for request
  • 404: No cart found
  • 500: unexpected error

func (*Service) Get

func (svc *Service) Get(w http.ResponseWriter, req *http.Request)

Get will return the Cart associated to the current user's session.

Status codes:

  • 200: OK
  • 400: No session found for request
  • 404: No cart found for session
  • 500: unexpected error

func (*Service) GetWithID

func (svc *Service) GetWithID(w http.ResponseWriter, req *http.Request)

GetWithID will return the Cart if found.

Errors:

  • NotAuthorizedError if user is not authorized
  • ErrCartNotFound if cart is not found

Status Codes:

  • 200: OK
  • 403: Forbidden
  • 404: Cart not found

func (*Service) Router

func (svc *Service) Router(base string) *chi.Mux

func (*Service) Update

func (svc *Service) Update(w http.ResponseWriter, req *http.Request)

Update will update the Cart for the current session. It will reject the Cart if the ID suplied does not match.

Status codes:

  • 200: Updated successfully
  • 400: No session found for request
  • 403: Cart ID is not the ID matching this session's Cart
  • 404: No cart found for this session
  • 500: unexpected error

func (*Service) UpdateWithID

func (svc *Service) UpdateWithID(w http.ResponseWriter, req *http.Request)

Update will update the Cart. It will override the Cart ID to ensure no accidental changes.

Status codes:

  • 200: Updated successfully
  • 400: No session found for request
  • 404: No cart found
  • 500: unexpected error

type UpdateCartRequest

type UpdateCartRequest = Request[Cart]

type UpdateCartResponse

type UpdateCartResponse = Response[Cart]

type UserContext

type UserContext struct {
	UserID       string
	SessionToken string
}

func (UserContext) IsLoggedIn

func (u UserContext) IsLoggedIn() bool

type UserContextFetcher

type UserContextFetcher interface {
	GetUserContext(req *http.Request) (UserContext, error)
}

UserContextFetcher encapsulates functionality for fetching session tokens and user IDs from a request. Returns ErrSessionNotFound if no session could be found.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL