├── .editorconfig ├── .gitignore ├── .runner.conf ├── Dockerfile ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE.md ├── README.md ├── api ├── context │ ├── context.go │ ├── context_test.go │ └── doc.go ├── decl.go ├── doc.go ├── helpers.go ├── interfaces.go ├── manager.go ├── rest │ ├── decl.go │ ├── doc.go │ ├── i_application_context.go │ ├── i_restservice.go │ └── init.go └── session │ ├── decl.go │ ├── doc.go │ ├── service.go │ ├── service_filesystem.go │ ├── service_memcache.go │ ├── service_memory.go │ ├── service_redis.go │ ├── session.go │ └── session_test.go ├── app ├── actors │ ├── blog │ │ ├── init.go │ │ └── post │ │ │ ├── api.go │ │ │ ├── decl.go │ │ │ ├── i_post.go │ │ │ ├── init.go │ │ │ └── post_api_test.go │ ├── cart │ │ ├── add_item_test.go │ │ ├── api.go │ │ ├── config.go │ │ ├── decl.go │ │ ├── i_cart.go │ │ ├── i_cart_item.go │ │ ├── i_model.go │ │ ├── i_storable.go │ │ └── init.go │ ├── category │ │ ├── api.go │ │ ├── category.i_category.go │ │ ├── category.i_listable.go │ │ ├── category.i_media.go │ │ ├── category.i_model.go │ │ ├── category.i_object.go │ │ ├── category.i_storable.go │ │ ├── collection.i_category_collection.go │ │ ├── collection.i_collection.go │ │ ├── collection.i_model.go │ │ ├── decl.go │ │ └── init.go │ ├── checkout │ │ ├── api.go │ │ ├── config.go │ │ ├── decl.go │ │ ├── i_checkout.go │ │ ├── i_model.go │ │ ├── i_object.go │ │ ├── init.go │ │ ├── internal.go │ │ └── price_adjustment_test.go │ ├── cms │ │ ├── block │ │ │ ├── api.go │ │ │ ├── block.i_cms_block.go │ │ │ ├── block.i_listable.go │ │ │ ├── block.i_model.go │ │ │ ├── block.i_object.go │ │ │ ├── block.i_storable.go │ │ │ ├── collection.i_cms_block_collection.go │ │ │ ├── collection.i_collection.go │ │ │ ├── collection.i_model.go │ │ │ ├── decl.go │ │ │ └── init.go │ │ ├── init.go │ │ ├── media │ │ │ ├── api.go │ │ │ ├── decl.go │ │ │ ├── helpers.go │ │ │ └── init.go │ │ └── page │ │ │ ├── api.go │ │ │ ├── collection.i_cms_page_collection.go │ │ │ ├── collection.i_collection.go │ │ │ ├── collection.i_model.go │ │ │ ├── decl.go │ │ │ ├── init.go │ │ │ ├── page.i_cms_page.go │ │ │ ├── page.i_listable.go │ │ │ ├── page.i_model.go │ │ │ ├── page.i_object.go │ │ │ └── page.i_storable.go │ ├── discount │ │ ├── coupon │ │ │ ├── api.go │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ ├── i_price_adjustment.go │ │ │ └── init.go │ │ ├── giftcard │ │ │ ├── api.go │ │ │ ├── config.go │ │ │ ├── cron.go │ │ │ ├── decl.go │ │ │ ├── handlers.go │ │ │ ├── i_price_adjustment.go │ │ │ ├── i_shipping_method.go │ │ │ └── init.go │ │ └── saleprice │ │ │ ├── api.go │ │ │ ├── collection.go │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ ├── delegate.go │ │ │ ├── init.go │ │ │ ├── price_adjustment.go │ │ │ └── saleprice.go │ ├── order │ │ ├── api.go │ │ ├── config.go │ │ ├── decl.go │ │ ├── init.go │ │ ├── internal.go │ │ ├── item.i_object.go │ │ ├── item.i_order_item.go │ │ ├── item_collection.i_listable.go │ │ ├── item_collection.i_model.go │ │ ├── item_collection.i_order_item_collection.go │ │ ├── order.i_listable.go │ │ ├── order.i_model.go │ │ ├── order.i_object.go │ │ ├── order.i_order.go │ │ ├── order.i_storable.go │ │ ├── order_collection.i_listable.go │ │ ├── order_collection.i_model.go │ │ └── order_collection.i_order_collection.go │ ├── other │ │ ├── emma │ │ │ ├── api.go │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ ├── emma.go │ │ │ ├── handlers.go │ │ │ └── init.go │ │ ├── friendmail │ │ │ ├── api.go │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ └── init.go │ │ ├── grouping │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ ├── handlers.go │ │ │ └── init.go │ │ ├── mailchimp │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ ├── handlers.go │ │ │ ├── init.go │ │ │ └── mailchimp_test.go │ │ ├── shipstation │ │ │ ├── api.go │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ ├── init.go │ │ │ ├── internal.go │ │ │ └── shipstation_test.go │ │ ├── trustpilot │ │ │ ├── api.go │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ ├── handlers.go │ │ │ ├── handlers_test.go │ │ │ └── init.go │ │ └── vantagepoint │ │ │ ├── actors │ │ │ ├── decl.go │ │ │ ├── diskstorage.go │ │ │ ├── inventorycsv.go │ │ │ ├── test │ │ │ │ ├── inventorycsv_test.go │ │ │ │ └── uploadsprocessor_test.go │ │ │ ├── uploadsprocessor.go │ │ │ └── uploadsprocessor_test.go │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ ├── init.go │ │ │ ├── internal.go │ │ │ └── schedule.go │ ├── payment │ │ ├── authorizenet │ │ │ ├── api.go │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ ├── i_payment_method-rest.go │ │ │ ├── i_payment_method.go │ │ │ ├── init.go │ │ │ ├── internal.go │ │ │ └── rest_method_integration_test.go │ │ ├── braintree │ │ │ ├── cc_method_integration_test.go │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ ├── i_payment_method-cc.go │ │ │ ├── init.go │ │ │ └── internal.go │ │ ├── checkmo │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ ├── i_payment_method.go │ │ │ └── init.go │ │ ├── paypal │ │ │ ├── api.go │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ ├── i_payment_method-express.go │ │ │ ├── i_payment_method-payflow.go │ │ │ ├── i_payment_method-rest.go │ │ │ ├── init.go │ │ │ └── internal.go │ │ ├── stripe │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ ├── i_payment_method.go │ │ │ ├── init.go │ │ │ └── internal.go │ │ └── zeropay │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ ├── i_payment_method.go │ │ │ └── init.go │ ├── product │ │ ├── api.go │ │ ├── apply_options_test.go │ │ ├── collection.go │ │ ├── decl.go │ │ ├── init.go │ │ ├── product.go │ │ └── review │ │ │ ├── api.go │ │ │ ├── decl.go │ │ │ ├── init.go │ │ │ └── review_api_test.go │ ├── reporting │ │ ├── api.go │ │ ├── decl.go │ │ └── init.go │ ├── rts │ │ ├── api.go │ │ ├── config.go │ │ ├── decl.go │ │ ├── handlers.go │ │ ├── init.go │ │ └── internal.go │ ├── seo │ │ ├── api.go │ │ ├── collection.go │ │ ├── decl.go │ │ ├── helpers.go │ │ ├── init.go │ │ ├── listable.go │ │ ├── seo.go │ │ └── seo_test.go │ ├── shipping │ │ ├── fedex │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ ├── i_shipping_method.go │ │ │ └── init.go │ │ ├── flatrate │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ ├── i_shipping_method.go │ │ │ └── init.go │ │ ├── flatweight │ │ │ ├── config.go │ │ │ ├── config_test.go │ │ │ ├── decl.go │ │ │ ├── i_shipping_method.go │ │ │ └── init.go │ │ └── usps │ │ │ ├── config.go │ │ │ ├── decl.go │ │ │ ├── i_shipping_method.go │ │ │ └── init.go │ ├── stock │ │ ├── api.go │ │ ├── collection.go │ │ ├── config.go │ │ ├── decl.go │ │ ├── helpers.go │ │ ├── i_delegate.go │ │ ├── i_listable.go │ │ ├── i_stock.go │ │ ├── init.go │ │ └── stock_test.go │ ├── subscription │ │ ├── api.go │ │ ├── collection.i_collection.go │ │ ├── collection.i_model.go │ │ ├── collection.i_subscription_collection.go │ │ ├── config.go │ │ ├── cron.go │ │ ├── decl.go │ │ ├── handlers.go │ │ ├── init.go │ │ ├── internal.go │ │ ├── subscription.i_listable.go │ │ ├── subscription.i_model.go │ │ ├── subscription.i_object.go │ │ ├── subscription.i_storable.go │ │ └── subscription.i_subscription.go │ ├── swatch │ │ ├── api.go │ │ ├── decl.go │ │ └── init.go │ ├── tax │ │ ├── api.go │ │ ├── decl.go │ │ ├── i_price_adjustment.go │ │ └── init.go │ ├── visitor │ │ ├── address │ │ │ ├── address.i_listable.go │ │ │ ├── address.i_model.go │ │ │ ├── address.i_object.go │ │ │ ├── address.i_storable.go │ │ │ ├── address.i_visitor_address.go │ │ │ ├── api.go │ │ │ ├── collection.i_collection.go │ │ │ ├── collection.i_model.go │ │ │ ├── collection.i_visitor_address_collection.go │ │ │ ├── decl.go │ │ │ └── init.go │ │ ├── api.go │ │ ├── collection.i_collection.go │ │ ├── collection.i_model.go │ │ ├── collection.i_visitor_collection.go │ │ ├── config.go │ │ ├── decl.go │ │ ├── init.go │ │ ├── token │ │ │ ├── api.go │ │ │ ├── collection.i_collection.go │ │ │ ├── collection.i_model.go │ │ │ ├── collection.i_visitor_card_collection.go │ │ │ ├── decl.go │ │ │ ├── init.go │ │ │ ├── token.i_model.go │ │ │ ├── token.i_object.go │ │ │ ├── token.i_storable.go │ │ │ └── token.i_visitor_card.go │ │ ├── visitor.i_listable.go │ │ ├── visitor.i_model.go │ │ ├── visitor.i_object.go │ │ ├── visitor.i_storable.go │ │ └── visitor.i_visitor.go │ └── xdomain │ │ ├── api.go │ │ ├── decl.go │ │ └── init.go ├── api.go ├── config.go ├── decl.go ├── doc.go ├── helpers.go ├── helpers │ ├── attributes │ │ ├── custom.go │ │ ├── decl.go │ │ ├── external.go │ │ ├── external_test.go │ │ ├── init.go │ │ └── other_test.go │ └── objectref │ │ ├── decl.go │ │ ├── i_object.go │ │ └── i_storable.go ├── init.go ├── manager.go └── models │ ├── blog │ └── post │ │ ├── helpers.go │ │ └── interfaces.go │ ├── cart │ ├── helpers.go │ └── interfaces.go │ ├── category │ ├── helpers.go │ └── interfaces.go │ ├── checkout │ ├── decl.go │ ├── helpers.go │ ├── interfaces.go │ └── manager.go │ ├── cms │ ├── helpers.go │ └── interfaces.go │ ├── decl.go │ ├── discount │ └── saleprice │ │ ├── decl.go │ │ ├── helpers.go │ │ └── interfaces.go │ ├── helpers.go │ ├── interfaces.go │ ├── manager.go │ ├── order │ ├── helpers.go │ └── interfaces.go │ ├── product │ ├── helpers.go │ ├── interfaces.go │ └── manager.go │ ├── seo │ ├── helpers.go │ ├── interfaces.go │ └── manager.go │ ├── stock │ ├── helpers.go │ └── interfaces.go │ ├── subscription │ ├── decl.go │ ├── helpers.go │ └── interfaces.go │ └── visitor │ ├── helpers.go │ └── interfaces.go ├── basebuild ├── build.go ├── build_mongo.go ├── build_mysql.go ├── build_postgres.go ├── build_sqlite.go └── doc.go ├── bin ├── bootstrap.sh ├── build-docker-image.sh ├── deploy.sh ├── docker-entrypoint.sh ├── make.sh ├── mongo-dump-csv.sh ├── prod_deploy.sh ├── slack_notifier.py └── update-docs.sh ├── db ├── dbconnector.go ├── doc.go ├── helpers.go ├── interfaces.go ├── manager.go ├── mongo │ ├── collection.i_dbcollection.go │ ├── collection.internal.go │ ├── decl.go │ ├── doc.go │ ├── engine.i_dbconnector.go │ ├── engine.i_dbengine.go │ ├── init.go │ └── utils.go ├── mysql │ ├── collection.i_dbcollection.go │ ├── collection.internal.go │ ├── dbcollection_test.go │ ├── decl.go │ ├── doc.go │ ├── engine.i_dbconnector.go │ ├── engine.i_dbengine.go │ ├── init.go │ └── internal.go ├── postgres │ ├── collection.i_dbcollection.go │ ├── collection.internal.go │ ├── decl.go │ ├── doc.go │ ├── engine.i_dbconnector.go │ ├── engine.i_dbengine.go │ ├── init.go │ ├── internal.go │ └── postgres_test.go ├── sqlite │ ├── collection.i_dbcollection.go │ ├── collection.internal.go │ ├── dbcollection_test.go │ ├── decl.go │ ├── doc.go │ ├── engine.i_dbconnector.go │ ├── engine.i_dbengine.go │ ├── init.go │ └── internal.go └── test.go ├── doc.go ├── env ├── config │ ├── api.go │ ├── decl.go │ ├── doc.go │ ├── i_config.go │ ├── i_impex_model.go │ └── init.go ├── cron │ ├── api.go │ ├── decl.go │ ├── doc.go │ ├── i_schedule.go │ ├── i_scheduler.go │ └── init.go ├── doc.go ├── errorbus │ ├── config.go │ ├── decl.go │ ├── doc.go │ ├── i_error_bus.go │ ├── i_ottemo_error.go │ └── init.go ├── eventbus │ ├── decl.go │ ├── doc.go │ ├── i_event_bus.go │ └── init.go ├── helpers.go ├── ini │ ├── decl.go │ ├── doc.go │ ├── i_iniconfig.go │ └── init.go ├── interfaces.go ├── logger │ ├── config.go │ ├── decl.go │ ├── doc.go │ ├── i_logger.go │ └── init.go └── manager.go ├── foundation.conf ├── impex ├── api.go ├── commands.go ├── csv.go ├── decl.go ├── doc.go ├── helpers.go ├── init.go ├── interfaces.go └── manager.go ├── main.go ├── media ├── doc.go ├── fsmedia │ ├── api.go │ ├── config.go │ ├── decl.go │ ├── doc.go │ ├── i_media_storage.go │ ├── image.go │ └── init.go ├── interfaces.go └── manager.go ├── ottemo.sample.ini ├── swagger.json ├── test ├── checkout_test.go ├── doc.go ├── generic.go ├── helpers.go └── product_test.go ├── tests └── jmeter │ ├── address.txt │ ├── oneStepCheckout.jmx │ └── visitor.txt └── utils ├── crypt.go ├── datatypes.go ├── doc.go ├── generic.go ├── generic_test.go ├── json.go ├── rounding_test.go ├── sorting.go ├── sorting_test.go ├── sync.go ├── sync_test.go ├── templates.go └── timezones.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = LF 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.go] 12 | indent_style = tab 13 | indent_size = 8 14 | 15 | [*.{yml,json}] 16 | indent_style=space 17 | indent_size=2 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | 22 | [*.{html,js,css,scss,xml}] 23 | indent_size=2 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | *.gz 6 | 7 | # Folders 8 | _obj 9 | _test 10 | tags 11 | .tags 12 | .idea 13 | tmp 14 | release 15 | var 16 | vendor 17 | .vagrant 18 | .vscode 19 | kg-media 20 | 21 | # Architecture specific extensions/prefixes 22 | *.[568vq] 23 | [568vq].out 24 | 25 | *.cgo1.go 26 | *.cgo2.c 27 | _cgo_defun.c 28 | _cgo_gotypes.go 29 | _cgo_export.* 30 | 31 | _testmain.go 32 | npm-debug.log 33 | 34 | *.exe 35 | gin-bin 36 | foundation 37 | commerce 38 | 39 | # Configuration files 40 | *.iml 41 | glide.lock 42 | 43 | # test dbs 44 | db/sqlite/test/ottemo.db 45 | ottemo.db 46 | ottemo.ini 47 | ottemo.ini.bak 48 | .DS_Store 49 | -------------------------------------------------------------------------------- /.runner.conf: -------------------------------------------------------------------------------- 1 | root: . 2 | tmp_path: ./tmp 3 | build_name: runner-build 4 | build_log: runner-build-errors.log 5 | valid_ext: .go, .tpl, .tmpl, .html 6 | ignored: assets, tmp, media, tags 7 | build_delay: 600 8 | colors: 1 9 | log_color_main: cyan 10 | log_color_build: yellow 11 | log_color_runner: green 12 | log_color_watcher: magenta 13 | log_color_app: 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.5 2 | 3 | RUN apk add --no-cache ca-certificates gawk 4 | 5 | RUN mkdir -pv /home/ottemo/commerce 6 | RUN mkdir -pv /home/ottemo/commerce/var/log 7 | RUN mkdir -pv /home/ottemo/commerce/var/session 8 | 9 | COPY commerce /home/ottemo/commerce/ 10 | 11 | # create links for proper logging 12 | RUN ln -sf /dev/stdout /home/ottemo/commerce/var/log/cron.log 13 | RUN ln -sf /dev/stdout /home/ottemo/commerce/var/log/events.log 14 | RUN ln -sf /dev/stdout /home/ottemo/commerce/var/log/paypal.log 15 | RUN ln -sf /dev/stdout /home/ottemo/commerce/var/log/rest.log 16 | RUN ln -sf /dev/stdout /home/ottemo/commerce/var/log/subscription.log 17 | RUN ln -sf /dev/stdout /home/ottemo/commerce/var/log/mongo.log 18 | RUN ln -sf /dev/stdout /home/ottemo/commerce/var/log/events.log 19 | RUN ln -sf /dev/stdout /home/ottemo/commerce/var/log/models.log 20 | RUN ln -sf /dev/stdout /home/ottemo/commerce/var/log/usps.log 21 | RUN ln -sf /dev/stdout /home/ottemo/commerce/var/log/impex.log 22 | RUN ln -sf /dev/stdout /home/ottemo/commerce/var/log/product 23 | RUN ln -sf /dev/stderr /home/ottemo/commerce/var/log/errors.log 24 | 25 | COPY bin/docker-entrypoint.sh /home/ottemo/commerce/ 26 | 27 | EXPOSE 3000 28 | WORKDIR /home/ottemo/commerce 29 | CMD ./docker-entrypoint.sh 30 | -------------------------------------------------------------------------------- /api/decl.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | ) 6 | 7 | // Package global constants 8 | const ( 9 | ConstRESTActionParameter = "action" 10 | 11 | ConstSessionKeyAdminRights = "adminRights" // session key used to flag that user have admin rights 12 | ConstSessionCookieName = "OTTEMOSESSION" // cookie name which should contain sessionID 13 | ConstSessionKeyTimeZone = "timeZone" // session key for setting time zone 14 | 15 | ConstGETAuthParamName = "auth" 16 | ConstConfigPathStoreRootLogin = "general.store.root_login" 17 | ConstConfigPathStoreRootPassword = "general.store.root_password" 18 | 19 | ConstErrorModule = "api" 20 | ConstErrorLevel = env.ConstErrorLevelHelper 21 | ) 22 | 23 | // ApplicationContext is a type you can embed in your model for application context support 24 | type ApplicationContext struct{ InterfaceApplicationContext } 25 | 26 | // GetApplicationContext returns current application context or nil 27 | func (it *ApplicationContext) GetApplicationContext() InterfaceApplicationContext { 28 | return it.InterfaceApplicationContext 29 | } 30 | 31 | // SetApplicationContext assigns given application context to type 32 | func (it *ApplicationContext) SetApplicationContext(context InterfaceApplicationContext) error { 33 | it.InterfaceApplicationContext = context 34 | return nil 35 | } 36 | 37 | // StructRestRedirect is a structure you should return in API handler function if redirect needed 38 | type StructRestRedirect struct { 39 | Result interface{} 40 | Location string 41 | 42 | DoRedirect bool 43 | } 44 | 45 | // FuncAPIHandler is an API handler callback function 46 | type FuncAPIHandler func(context InterfaceApplicationContext) (interface{}, error) 47 | 48 | // FuncAPIResultHandler is an API result handler callback function. 49 | // It suppesod to be called by async API handler like api.APIAsyncHandler. 50 | type FuncAPIResultHandler func(context InterfaceApplicationContext, result interface{}, err error) 51 | -------------------------------------------------------------------------------- /api/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Ottemo. All rights reserved. 2 | 3 | /* 4 | Package api represents API services abstraction. It provides a set of interfaces and helpers for a packages to extend 5 | application with new API endpoints as well as to interact with other components of application through them. 6 | 7 | As application components (actor packages) should not interact between them directly them should use this package to make 8 | indirect calls between the. Interaction with a session manager also should happen through this package. 9 | */ 10 | package api 11 | -------------------------------------------------------------------------------- /api/rest/decl.go: -------------------------------------------------------------------------------- 1 | package rest 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | 7 | "github.com/julienschmidt/httprouter" 8 | "github.com/ottemo/commerce/api" 9 | "github.com/ottemo/commerce/env" 10 | ) 11 | 12 | // Package global constants 13 | const ( 14 | ConstDebugLogStorage = "rest.log" // log storage for debug log records 15 | 16 | ConstErrorModule = "api/rest" 17 | ConstErrorLevel = env.ConstErrorLevelService 18 | 19 | ConstConfigPathAPI = "api" 20 | ConstConfigPathAPILog = "api.log" 21 | ConstConfigPathAPILogEnable = "api.log.enable" 22 | ConstConfigPathAPILogExclude = "api.log.exclude" 23 | ) 24 | 25 | // DefaultRestService is a default implementer of InterfaceRestService 26 | // declared in "github.com/ottemo/commerce/api" package 27 | type DefaultRestService struct { 28 | ListenOn string 29 | Router *httprouter.Router 30 | Handlers []string 31 | } 32 | 33 | // DefaultRestApplicationContext is a structure to hold API request related information 34 | type DefaultRestApplicationContext struct { 35 | ResponseWriter http.ResponseWriter 36 | Request *http.Request 37 | RequestParameters map[string]string 38 | RequestArguments map[string]string 39 | RequestContent interface{} 40 | RequestFiles map[string]io.Reader 41 | 42 | Session api.InterfaceSession 43 | ContextValues map[string]interface{} 44 | Result interface{} 45 | } 46 | -------------------------------------------------------------------------------- /api/rest/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Ottemo. All rights reserved. 2 | 3 | /* 4 | Package rest is a default implementation of a RESTful server. That package provides "InterfaceRestService" functionality 5 | declared in "github.com/ottemo/commerce/api" package. 6 | 7 | Package stands on a top of "github.com/julienschmidt/httprouter" package. 8 | 9 | RESTful server is a HTTP protocol accessible service you can use to interact with application. Refer to "http://[url-base]/" 10 | URL fo a list of API function application build providing. ("http://localhost:300" by default). The default interaction 11 | protocol for API functions is "application/json" (but not limited to, some API returns raw data, others "plain text", some 12 | of them supports couple content types). 13 | 14 | Ottemo API calls are supplied with ApplicationContext abstraction (refer "InterfaceApplicationContext"), to transfer API 15 | call related "attributes" and "content". For REST server attributes are parameters specified in URL string whereas content 16 | is a HTTP Request content. Notice that URL arguments are both - REST resource required parameters and optional URL parameters. 17 | 18 | Example: 19 | endpoint: /category/:categoryID/products 20 | api call: http://localhost/category/5488485b49c43d4283000067/products?action=count,sku=~10 21 | 22 | So, the arguments provided to API handler function are: 23 | categoryID="5488485b49c43d4283000067", action="count", sku="~10" (with string values) 24 | 25 | Session specification addressed to "OTTEMOSESSION=[sessionID]" COOKIE value. Each request with unspecified session will 26 | be supplied with new one session. SessionID will be returned in mentioned COOKIE value. 27 | */ 28 | package rest 29 | -------------------------------------------------------------------------------- /api/session/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Ottemo. All rights reserved. 2 | 3 | /* 4 | Package session is a default implementation of a application Session manager. That package provides "InterfaceSessionService" 5 | functionality declared in "github.com/ottemo/commerce/api" package. 6 | 7 | Sessions are API call related storage. So, each call to API function supplied with own separated storage which can holds 8 | a values related to that particular action. By default sessions have a lifetime, within that period application routines 9 | can hold information fo future usage for either themselves or other API calls. In order to use previously created session 10 | API call should specify sessionID within application context. 11 | */ 12 | package session 13 | -------------------------------------------------------------------------------- /api/session/service_memory.go: -------------------------------------------------------------------------------- 1 | // +build memsession 2 | 3 | // "service_memory.go" is a memory session storage - "memsession" build tag should be specified in order to use it 4 | // (session instances holds only on memory without flushing to longer term storage) 5 | 6 | package session 7 | 8 | import ( 9 | "github.com/ottemo/commerce/api" 10 | ) 11 | 12 | // init makes package self-initialization routine 13 | func init() { 14 | SessionService = InitDefaultSessionService() 15 | 16 | // service registration within system 17 | api.RegisterSessionService(SessionService) 18 | } 19 | -------------------------------------------------------------------------------- /api/session/session.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | // InterfaceSession implementation 4 | // ------------------------------- 5 | 6 | // GetID returns current session id 7 | func (it DefaultSession) GetID() string { 8 | return string(it) 9 | } 10 | 11 | // Get returns session value by a given key or nil - if not set 12 | func (it DefaultSession) Get(key string) interface{} { 13 | return SessionService.GetKey(string(it), key) 14 | } 15 | 16 | // Set assigns value to session key 17 | func (it DefaultSession) Set(key string, value interface{}) { 18 | SessionService.SetKey(string(it), key, value) 19 | } 20 | 21 | // IsEmpty checks if session contains data 22 | func (it DefaultSession) IsEmpty() bool { 23 | return SessionService.IsEmpty(it.GetID()) 24 | } 25 | 26 | // Touch updates session last modification time to current moment 27 | func (it DefaultSession) Touch() error { 28 | return SessionService.Touch(string(it)) 29 | } 30 | 31 | // Close makes current session instance expired 32 | func (it DefaultSession) Close() error { 33 | return SessionService.Close(string(it)) 34 | } 35 | -------------------------------------------------------------------------------- /api/session/session_test.go: -------------------------------------------------------------------------------- 1 | package session 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | "github.com/ottemo/commerce/utils" 6 | "math/rand" 7 | "testing" 8 | ) 9 | 10 | // TestSessionsConcurrency tests synchronisation mechanisms between go routines 11 | func TestSessionsConcurrency(t *testing.T) { 12 | var sessions []api.InterfaceSession 13 | 14 | const sessionsNumber = 100 15 | const routinesNumber = 1000 16 | 17 | sync := make(chan bool) 18 | 19 | // preparing set of sessions to work with 20 | for i := 0; i < sessionsNumber; i++ { 21 | session, err := api.NewSession() 22 | if err != nil { 23 | t.Error(err) 24 | } 25 | sessions = append(sessions, session) 26 | } 27 | 28 | // making go-routines storm 29 | for i := 0; i < routinesNumber; i++ { 30 | go func() { 31 | session := sessions[rand.Intn(sessionsNumber)] 32 | for i := 0; i < 100; i++ { 33 | key := utils.InterfaceToString(i) 34 | session.Set(key, utils.InterfaceToInt(session.Get(key))+1) 35 | } 36 | 37 | if err := SessionService.GC(); err != nil { 38 | t.Error(err) 39 | } 40 | 41 | sync <- true 42 | }() 43 | } 44 | 45 | // waiting till all routines finishes their stuff 46 | finished := 0 47 | for finished = 0; finished < routinesNumber; <-sync { 48 | finished++ 49 | } 50 | 51 | // closing all the sessions 52 | for i := 0; i < sessionsNumber; i++ { 53 | if err := sessions[i].Close(); err != nil { 54 | t.Error(err) 55 | } 56 | } 57 | } 58 | 59 | // BenchmarkIoOperations evaluates the performance of Set/Get operations 60 | func BenchmarkIoOperations(b *testing.B) { 61 | session, err := api.NewSession() 62 | if err != nil { 63 | b.Fail() 64 | } 65 | 66 | for i := 0; i < 100; i++ { 67 | session.Set("test", i) 68 | 69 | if result := session.Get("test"); result != i { 70 | b.Error("assigned value not matches:", result, "!=", i) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/actors/blog/init.go: -------------------------------------------------------------------------------- 1 | package blog 2 | 3 | import ( 4 | // self-initiabilizable sub-package 5 | _ "github.com/ottemo/commerce/app/actors/blog/post" 6 | ) 7 | -------------------------------------------------------------------------------- /app/actors/blog/post/decl.go: -------------------------------------------------------------------------------- 1 | // Package post is a default implementation of blog post related interfaces declared in 2 | // "github.com/ottemo/commerce/app/models/blog/post" package 3 | package post 4 | 5 | import ( 6 | "time" 7 | 8 | "github.com/ottemo/commerce/env" 9 | ) 10 | 11 | // Package global constants 12 | const ( 13 | ConstBlogPostCollectionName = "blog_post" 14 | 15 | ConstErrorModule = "blog" 16 | ConstErrorLevel = env.ConstErrorLevelActor 17 | ) 18 | 19 | // DefaultBlogPost is a default implementer of InterfaceBlogPost 20 | type DefaultBlogPost struct { 21 | id string 22 | 23 | identifier string 24 | 25 | title string 26 | excerpt string 27 | content string 28 | 29 | tags []interface{} 30 | featuredImage string 31 | 32 | createdAt time.Time 33 | updatedAt time.Time 34 | published bool 35 | } 36 | -------------------------------------------------------------------------------- /app/actors/blog/post/init.go: -------------------------------------------------------------------------------- 1 | package post 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | "github.com/ottemo/commerce/db" 6 | "github.com/ottemo/commerce/env" 7 | 8 | "github.com/ottemo/commerce/app/models" 9 | "github.com/ottemo/commerce/app/models/blog/post" 10 | ) 11 | 12 | // init makes package self-initialization routine 13 | func init() { 14 | blogPostInstance := new(DefaultBlogPost) 15 | var _ post.InterfaceBlogPost = blogPostInstance 16 | if err := models.RegisterModel(post.ConstModelNameBlogPost, blogPostInstance); err != nil { 17 | _ = env.ErrorDispatch(err) 18 | } 19 | 20 | db.RegisterOnDatabaseStart(setupDB) 21 | api.RegisterOnRestServiceStart(setupAPI) 22 | } 23 | 24 | // setupDB prepares system database for package usage 25 | func setupDB() error { 26 | var err error 27 | 28 | collection, err := db.GetCollection(ConstBlogPostCollectionName) 29 | if err != nil { 30 | return env.ErrorDispatch(err) 31 | } 32 | 33 | if err = collection.AddColumn("identifier", db.ConstTypeVarchar, true); err != nil { 34 | return env.ErrorDispatch(err) 35 | } 36 | if err = collection.AddColumn("published", db.ConstTypeBoolean, true); err != nil { 37 | return env.ErrorDispatch(err) 38 | } 39 | if err = collection.AddColumn("title", db.ConstTypeVarchar, false); err != nil { 40 | return env.ErrorDispatch(err) 41 | } 42 | if err = collection.AddColumn("excerpt", db.ConstTypeText, false); err != nil { 43 | return env.ErrorDispatch(err) 44 | } 45 | if err = collection.AddColumn("content", db.ConstTypeText, false); err != nil { 46 | return env.ErrorDispatch(err) 47 | } 48 | if err = collection.AddColumn("created_at", db.ConstTypeDatetime, false); err != nil { 49 | return env.ErrorDispatch(err) 50 | } 51 | if err = collection.AddColumn("updated_at", db.ConstTypeDatetime, false); err != nil { 52 | return env.ErrorDispatch(err) 53 | } 54 | if err = collection.AddColumn("tags", "[]"+db.ConstTypeText, false); err != nil { 55 | return env.ErrorDispatch(err) 56 | } 57 | if err = collection.AddColumn("featured_image", db.ConstTypeVarchar, false); err != nil { 58 | return env.ErrorDispatch(err) 59 | } 60 | 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /app/actors/cart/config.go: -------------------------------------------------------------------------------- 1 | package cart 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | ) 6 | 7 | func setupConfig() error { 8 | config := env.GetConfig() 9 | if config == nil { 10 | err := env.ErrorNew(ConstErrorModule, env.ConstErrorLevelStartStop, "fed0dee4-3409-4533-a445-998d2290569a", "Cart module is unable to obtain configuration settings, using nil instead") 11 | return env.ErrorDispatch(err) 12 | } 13 | 14 | err := config.RegisterItem(env.StructConfigItem{ 15 | Path: ConstConfigPathCartAbandonEmailSendTime, 16 | Value: "0", 17 | Type: env.ConstConfigTypeVarchar, 18 | Editor: "select", 19 | Options: map[string]string{ 20 | "0": "Never", 21 | "-24": "After 24 hours", 22 | }, 23 | Label: "Abandoned Cart Email - Send Time", 24 | Description: "If the customer abandons checkout, send them an email to complete their order.", 25 | Image: "", 26 | }, nil) 27 | 28 | if err != nil { 29 | return env.ErrorDispatch(err) 30 | } 31 | 32 | err = config.RegisterItem(env.StructConfigItem{ 33 | Path: ConstConfigPathCartAbandonEmailTemplate, 34 | Value: "", 35 | Type: env.ConstConfigTypeHTML, 36 | Editor: "multiline_text", 37 | Options: "", 38 | Label: "Abandoned Cart Email - Template", 39 | Description: "", 40 | Image: "", 41 | }, nil) 42 | 43 | if err != nil { 44 | return env.ErrorDispatch(err) 45 | } 46 | 47 | return nil 48 | } 49 | -------------------------------------------------------------------------------- /app/actors/cart/i_model.go: -------------------------------------------------------------------------------- 1 | package cart 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/cart" 6 | ) 7 | 8 | // GetModelName returns model name we have implementation for 9 | func (it *DefaultCart) GetModelName() string { 10 | return cart.ConstCartModelName 11 | } 12 | 13 | // GetImplementationName returns name of current model implementation 14 | func (it *DefaultCart) GetImplementationName() string { 15 | return "DefaultCart" 16 | } 17 | 18 | // New makes new instance of model 19 | func (it *DefaultCart) New() (models.InterfaceModel, error) { 20 | return &DefaultCart{ 21 | Items: make(map[int]cart.InterfaceCartItem), 22 | Info: make(map[string]interface{}), 23 | CustomInfo: make(map[string]interface{}), 24 | }, nil 25 | } 26 | -------------------------------------------------------------------------------- /app/actors/category/category.i_listable.go: -------------------------------------------------------------------------------- 1 | package category 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/category" 6 | ) 7 | 8 | // GetCollection returns collection of current instance type 9 | func (it *DefaultCategory) GetCollection() models.InterfaceCollection { 10 | model, _ := models.GetModel(category.ConstModelNameCategoryCollection) 11 | if result, ok := model.(category.InterfaceCategoryCollection); ok { 12 | return result 13 | } 14 | 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /app/actors/category/category.i_model.go: -------------------------------------------------------------------------------- 1 | package category 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/category" 6 | ) 7 | 8 | // GetModelName returns model name 9 | func (it *DefaultCategory) GetModelName() string { 10 | return category.ConstModelNameCategory 11 | } 12 | 13 | // GetImplementationName returns model implementation name 14 | func (it *DefaultCategory) GetImplementationName() string { 15 | return "Default" + category.ConstModelNameCategory 16 | } 17 | 18 | // New returns new instance of model implementation object 19 | func (it *DefaultCategory) New() (models.InterfaceModel, error) { 20 | return &DefaultCategory{ProductIds: make([]string, 0)}, nil 21 | } 22 | -------------------------------------------------------------------------------- /app/actors/category/collection.i_category_collection.go: -------------------------------------------------------------------------------- 1 | package category 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models/category" 5 | "github.com/ottemo/commerce/db" 6 | "github.com/ottemo/commerce/env" 7 | ) 8 | 9 | // GetDBCollection returns database collection 10 | func (it *DefaultCategoryCollection) GetDBCollection() db.InterfaceDBCollection { 11 | return it.listCollection 12 | } 13 | 14 | // ListCategories returns list of category model items 15 | func (it *DefaultCategoryCollection) ListCategories() []category.InterfaceCategory { 16 | var result []category.InterfaceCategory 17 | 18 | dbRecords, err := it.listCollection.Load() 19 | if err != nil { 20 | return result 21 | } 22 | 23 | for _, recordData := range dbRecords { 24 | categoryModel, err := category.GetCategoryModel() 25 | if err != nil { 26 | return result 27 | } 28 | if err := categoryModel.FromHashMap(recordData); err != nil { 29 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "f5cc61ae-cdff-4f38-9a72-12e235e586a5", err.Error()) 30 | } 31 | 32 | result = append(result, categoryModel) 33 | } 34 | 35 | return result 36 | } 37 | -------------------------------------------------------------------------------- /app/actors/category/collection.i_model.go: -------------------------------------------------------------------------------- 1 | package category 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/category" 6 | "github.com/ottemo/commerce/db" 7 | "github.com/ottemo/commerce/env" 8 | ) 9 | 10 | // GetModelName returns model name 11 | func (it *DefaultCategoryCollection) GetModelName() string { 12 | return category.ConstModelNameCategory 13 | } 14 | 15 | // GetImplementationName returns model implementation name 16 | func (it *DefaultCategoryCollection) GetImplementationName() string { 17 | return "Default" + category.ConstModelNameCategory 18 | } 19 | 20 | // New returns new instance of model implementation object 21 | func (it *DefaultCategoryCollection) New() (models.InterfaceModel, error) { 22 | dbCollection, err := db.GetCollection(ConstCollectionNameCategory) 23 | if err != nil { 24 | return nil, env.ErrorDispatch(err) 25 | } 26 | 27 | return &DefaultCategoryCollection{listCollection: dbCollection, listExtraAtributes: make([]string, 0)}, nil 28 | } 29 | -------------------------------------------------------------------------------- /app/actors/category/decl.go: -------------------------------------------------------------------------------- 1 | // Package category is a default implementation of interfaces declared in 2 | // "github.com/ottemo/commerce/app/models/category" package 3 | package category 4 | 5 | import ( 6 | "github.com/ottemo/commerce/app/models/category" 7 | "github.com/ottemo/commerce/db" 8 | "github.com/ottemo/commerce/env" 9 | ) 10 | 11 | // Package global constants 12 | const ( 13 | ConstCollectionNameCategory = "category" 14 | ConstCollectionNameCategoryProductJunction = "category_product" 15 | 16 | ConstErrorModule = "category" 17 | ConstErrorLevel = env.ConstErrorLevelActor 18 | 19 | ConstCategoryMediaTypeImage = "image" 20 | ) 21 | 22 | // DefaultCategory is a default implementer of InterfaceCategory 23 | type DefaultCategory struct { 24 | id string 25 | 26 | Enabled bool 27 | Name string 28 | Description string 29 | Image string 30 | Parent category.InterfaceCategory 31 | Path string 32 | ProductIds []string 33 | } 34 | 35 | // DefaultCategoryCollection is a default implementer of InterfaceCategoryCollection 36 | type DefaultCategoryCollection struct { 37 | listCollection db.InterfaceDBCollection 38 | listExtraAtributes []string 39 | } 40 | -------------------------------------------------------------------------------- /app/actors/checkout/decl.go: -------------------------------------------------------------------------------- 1 | // Package checkout is a default implementation of interfaces declared in 2 | // "github.com/ottemo/commerce/app/models/checkout" package 3 | package checkout 4 | 5 | import ( 6 | "github.com/ottemo/commerce/app/models/cart" 7 | "github.com/ottemo/commerce/app/models/checkout" 8 | "github.com/ottemo/commerce/env" 9 | ) 10 | 11 | // Package global constants 12 | const ( 13 | ConstErrorModule = "checkout" 14 | ConstErrorLevel = env.ConstErrorLevelActor 15 | ) 16 | 17 | // DefaultCheckout is a default implementer of InterfaceCheckout 18 | type DefaultCheckout struct { 19 | CartID string 20 | VisitorID string 21 | OrderID string 22 | 23 | SessionID string 24 | 25 | ShippingAddress map[string]interface{} 26 | BillingAddress map[string]interface{} 27 | 28 | PaymentMethodCode string 29 | ShippingMethodCode string 30 | 31 | ShippingRate checkout.StructShippingRate 32 | 33 | priceAdjustments []checkout.StructPriceAdjustment 34 | 35 | // should store details about applied adjustments for specific keys 36 | // 0 - cart, 1,2,3, .. n - index of cart item 37 | calculationDetailTotals map[int]map[string]float64 38 | cart cart.InterfaceCart 39 | 40 | Info map[string]interface{} 41 | 42 | calculateAmount float64 43 | 44 | // flags enables and disables during calculation to prevent recursion 45 | calculateFlag bool 46 | } 47 | -------------------------------------------------------------------------------- /app/actors/checkout/i_model.go: -------------------------------------------------------------------------------- 1 | package checkout 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/checkout" 6 | ) 7 | 8 | // GetModelName returns model name we have implementation for 9 | func (it *DefaultCheckout) GetModelName() string { 10 | return checkout.ConstCheckoutModelName 11 | } 12 | 13 | // GetImplementationName returns name of current model implementation 14 | func (it *DefaultCheckout) GetImplementationName() string { 15 | return "Default" + checkout.ConstCheckoutModelName 16 | } 17 | 18 | // New makes new instance of model 19 | func (it *DefaultCheckout) New() (models.InterfaceModel, error) { 20 | return &DefaultCheckout{Info: make(map[string]interface{})}, nil 21 | } 22 | -------------------------------------------------------------------------------- /app/actors/checkout/init.go: -------------------------------------------------------------------------------- 1 | package checkout 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | "github.com/ottemo/commerce/env" 6 | 7 | "github.com/ottemo/commerce/app/models" 8 | "github.com/ottemo/commerce/app/models/checkout" 9 | ) 10 | 11 | // init makes package self-initialization routine 12 | func init() { 13 | instance := new(DefaultCheckout) 14 | var _ checkout.InterfaceCheckout = instance 15 | if err := models.RegisterModel(checkout.ConstCheckoutModelName, instance); err != nil { 16 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "6dcce70e-7d29-46fb-a91a-6dc6d2836695", err.Error()) 17 | } 18 | 19 | api.RegisterOnRestServiceStart(setupAPI) 20 | env.RegisterOnConfigStart(setupConfig) 21 | } 22 | -------------------------------------------------------------------------------- /app/actors/cms/block/block.i_listable.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/cms" 6 | ) 7 | 8 | // GetCollection returns collection of current instance type 9 | func (it *DefaultCMSBlock) GetCollection() models.InterfaceCollection { 10 | model, _ := models.GetModel(cms.ConstModelNameCMSBlockCollection) 11 | if result, ok := model.(cms.InterfaceCMSBlockCollection); ok { 12 | return result 13 | } 14 | 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /app/actors/cms/block/block.i_model.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/cms" 6 | ) 7 | 8 | // GetModelName returns model name 9 | func (it *DefaultCMSBlock) GetModelName() string { 10 | return cms.ConstModelNameCMSBlock 11 | } 12 | 13 | // GetImplementationName returns model implementation name 14 | func (it *DefaultCMSBlock) GetImplementationName() string { 15 | return "DefaultCMSBlock" 16 | } 17 | 18 | // New returns new instance of model implementation object 19 | func (it *DefaultCMSBlock) New() (models.InterfaceModel, error) { 20 | return &DefaultCMSBlock{}, nil 21 | } 22 | -------------------------------------------------------------------------------- /app/actors/cms/block/collection.i_cms_block_collection.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models/cms" 5 | "github.com/ottemo/commerce/db" 6 | "github.com/ottemo/commerce/env" 7 | ) 8 | 9 | // GetDBCollection returns database collection 10 | func (it *DefaultCMSBlockCollection) GetDBCollection() db.InterfaceDBCollection { 11 | return it.listCollection 12 | } 13 | 14 | // ListCMSBlocks returns list of cms block model items 15 | func (it *DefaultCMSBlockCollection) ListCMSBlocks() []cms.InterfaceCMSBlock { 16 | var result []cms.InterfaceCMSBlock 17 | 18 | dbRecords, err := it.listCollection.Load() 19 | if err != nil { 20 | return result 21 | } 22 | 23 | for _, recordData := range dbRecords { 24 | cmsBlockModel, err := cms.GetCMSBlockModel() 25 | if err != nil { 26 | return result 27 | } 28 | if err := cmsBlockModel.FromHashMap(recordData); err != nil { 29 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "143f447e-4bb7-46e5-9481-523ccf48fc70", err.Error()) 30 | } 31 | 32 | result = append(result, cmsBlockModel) 33 | } 34 | 35 | return result 36 | } 37 | -------------------------------------------------------------------------------- /app/actors/cms/block/collection.i_model.go: -------------------------------------------------------------------------------- 1 | package block 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/cms" 6 | "github.com/ottemo/commerce/db" 7 | "github.com/ottemo/commerce/env" 8 | ) 9 | 10 | // GetModelName returns model name 11 | func (it *DefaultCMSBlockCollection) GetModelName() string { 12 | return cms.ConstModelNameCMSBlockCollection 13 | } 14 | 15 | // GetImplementationName returns model implementation name 16 | func (it *DefaultCMSBlockCollection) GetImplementationName() string { 17 | return "Default" + cms.ConstModelNameCMSBlockCollection 18 | } 19 | 20 | // New returns new instance of model implementation object 21 | func (it *DefaultCMSBlockCollection) New() (models.InterfaceModel, error) { 22 | dbCollection, err := db.GetCollection(ConstCmsBlockCollectionName) 23 | if err != nil { 24 | return nil, env.ErrorDispatch(err) 25 | } 26 | 27 | return &DefaultCMSBlockCollection{listCollection: dbCollection, listExtraAtributes: make([]string, 0)}, nil 28 | } 29 | -------------------------------------------------------------------------------- /app/actors/cms/block/decl.go: -------------------------------------------------------------------------------- 1 | // Package block is a default implementation of cms block related interfaces declared in 2 | // "github.com/ottemo/commerce/app/models/csm" package 3 | package block 4 | 5 | import ( 6 | "github.com/ottemo/commerce/db" 7 | "github.com/ottemo/commerce/env" 8 | "time" 9 | ) 10 | 11 | // Package global constants 12 | const ( 13 | ConstCmsBlockCollectionName = "cms_block" 14 | 15 | ConstErrorModule = "cms/block" 16 | ConstErrorLevel = env.ConstErrorLevelActor 17 | ) 18 | 19 | // DefaultCMSBlock is a default implementer of InterfaceCMSBlock 20 | type DefaultCMSBlock struct { 21 | id string 22 | 23 | Identifier string 24 | Content string 25 | 26 | CreatedAt time.Time 27 | UpdatedAt time.Time 28 | } 29 | 30 | // DefaultCMSBlockCollection is a default implementer of InterfaceCMSBlockCollection 31 | type DefaultCMSBlockCollection struct { 32 | listCollection db.InterfaceDBCollection 33 | listExtraAtributes []string 34 | } 35 | -------------------------------------------------------------------------------- /app/actors/cms/init.go: -------------------------------------------------------------------------------- 1 | // Package cms is just a grouping container for sub-packages auto init 2 | package cms 3 | 4 | import ( 5 | // self-initiabilizable sub-package 6 | _ "github.com/ottemo/commerce/app/actors/cms/block" 7 | 8 | // self-initiabilizable sub-package 9 | _ "github.com/ottemo/commerce/app/actors/cms/page" 10 | 11 | // self-initiabilizable sub-package 12 | _ "github.com/ottemo/commerce/app/actors/cms/media" 13 | ) 14 | -------------------------------------------------------------------------------- /app/actors/cms/media/decl.go: -------------------------------------------------------------------------------- 1 | // Package media is a default implementation of cms page related interfaces declared in 2 | // "github.com/ottemo/commerce/app/models/cms" package 3 | package media 4 | 5 | import ( 6 | "github.com/ottemo/commerce/env" 7 | "github.com/ottemo/commerce/media" 8 | ) 9 | 10 | // Package global constants 11 | const ( 12 | ConstErrorModule = "cms/media" 13 | ConstErrorLevel = env.ConstErrorLevelActor 14 | 15 | ConstStorageModel = "cms" 16 | ConstStorageObject = "media" 17 | ) 18 | 19 | var ( 20 | mediaStorage media.InterfaceMediaStorage 21 | ) 22 | -------------------------------------------------------------------------------- /app/actors/cms/media/helpers.go: -------------------------------------------------------------------------------- 1 | package media 2 | 3 | import ( 4 | "github.com/ottemo/commerce/media" 5 | ) 6 | 7 | // correctMediaType returns only supported media type according to srcMediaType specified 8 | func correctMediaType(srcMediaType string) string { 9 | var mediaType = srcMediaType 10 | 11 | if len(srcMediaType) == 0 { 12 | mediaType = media.ConstMediaTypeImage 13 | } else if mediaType != media.ConstMediaTypeImage { 14 | mediaType = media.ConstMediaTypeDocument 15 | } 16 | 17 | return mediaType 18 | } 19 | -------------------------------------------------------------------------------- /app/actors/cms/media/init.go: -------------------------------------------------------------------------------- 1 | package media 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | "github.com/ottemo/commerce/app" 6 | "github.com/ottemo/commerce/env" 7 | "github.com/ottemo/commerce/media" 8 | "github.com/ottemo/commerce/utils" 9 | ) 10 | 11 | // init makes package self-initialization routine 12 | func init() { 13 | api.RegisterOnRestServiceStart(setupAPI) 14 | app.OnAppStart(onAppStart) 15 | 16 | if err := utils.RegisterTemplateFunction("media", mediaTemplateDirective); err != nil { 17 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "706a59bb-cfdd-4e26-b8f1-42444daa3170", err.Error()) 18 | } 19 | } 20 | 21 | func onAppStart() error { 22 | mediaStorageInstance, err := media.GetMediaStorage() 23 | if err != nil { 24 | return env.ErrorDispatch(err) 25 | } 26 | 27 | mediaStorage = mediaStorageInstance 28 | return nil 29 | } 30 | 31 | // mediaTemplateDirective - for adding image to pages 32 | // - use {{media "mediaName" .}} to fetch image URL 33 | // - Currently this method supports only images, but it is not used anywhere 34 | func mediaTemplateDirective(args ...interface{}) (string, error) { 35 | mediaName := "" 36 | if len(args) > 0 { 37 | mediaName = utils.InterfaceToString(args[0]) 38 | } 39 | imagePath, err := mediaStorage.GetMediaPath(ConstStorageModel, ConstStorageObject, media.ConstMediaTypeImage) 40 | if err != nil { 41 | return "", env.ErrorDispatch(err) 42 | } 43 | 44 | commerceURL := app.GetcommerceURL(imagePath + mediaName) 45 | 46 | return "", nil 47 | } 48 | -------------------------------------------------------------------------------- /app/actors/cms/page/collection.i_cms_page_collection.go: -------------------------------------------------------------------------------- 1 | package page 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models/cms" 5 | "github.com/ottemo/commerce/db" 6 | "github.com/ottemo/commerce/env" 7 | ) 8 | 9 | // GetDBCollection returns database collection 10 | func (it *DefaultCMSPageCollection) GetDBCollection() db.InterfaceDBCollection { 11 | return it.listCollection 12 | } 13 | 14 | // ListCMSPages returns list of cms page model items 15 | func (it *DefaultCMSPageCollection) ListCMSPages() []cms.InterfaceCMSPage { 16 | var result []cms.InterfaceCMSPage 17 | 18 | dbRecords, err := it.listCollection.Load() 19 | if err != nil { 20 | return result 21 | } 22 | 23 | for _, recordData := range dbRecords { 24 | cmsPageModel, err := cms.GetCMSPageModel() 25 | if err != nil { 26 | return result 27 | } 28 | if err := cmsPageModel.FromHashMap(recordData); err != nil { 29 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "8e74b4b4-08e9-44d5-805d-e158f5af518c", err.Error()) 30 | } 31 | 32 | result = append(result, cmsPageModel) 33 | } 34 | 35 | return result 36 | } 37 | -------------------------------------------------------------------------------- /app/actors/cms/page/collection.i_model.go: -------------------------------------------------------------------------------- 1 | package page 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/cms" 6 | "github.com/ottemo/commerce/db" 7 | "github.com/ottemo/commerce/env" 8 | ) 9 | 10 | // GetModelName returns model name 11 | func (it *DefaultCMSPageCollection) GetModelName() string { 12 | return cms.ConstModelNameCMSPageCollection 13 | } 14 | 15 | // GetImplementationName returns model implementation name 16 | func (it *DefaultCMSPageCollection) GetImplementationName() string { 17 | return "Default" + cms.ConstModelNameCMSPageCollection 18 | } 19 | 20 | // New returns new instance of model implementation object 21 | func (it *DefaultCMSPageCollection) New() (models.InterfaceModel, error) { 22 | dbCollection, err := db.GetCollection(ConstCmsPageCollectionName) 23 | if err != nil { 24 | return nil, env.ErrorDispatch(err) 25 | } 26 | 27 | return &DefaultCMSPageCollection{listCollection: dbCollection, listExtraAtributes: make([]string, 0)}, nil 28 | } 29 | -------------------------------------------------------------------------------- /app/actors/cms/page/decl.go: -------------------------------------------------------------------------------- 1 | // Package page is a default implementation of cms page related interfaces declared in 2 | // "github.com/ottemo/commerce/app/models/csm" package 3 | package page 4 | 5 | import ( 6 | "github.com/ottemo/commerce/db" 7 | "github.com/ottemo/commerce/env" 8 | "time" 9 | ) 10 | 11 | // Package global constants 12 | const ( 13 | ConstCmsPageCollectionName = "cms_page" 14 | 15 | ConstErrorModule = "cms/page" 16 | ConstErrorLevel = env.ConstErrorLevelActor 17 | ) 18 | 19 | // DefaultCMSPage is a default implementer of InterfaceCMSPage 20 | type DefaultCMSPage struct { 21 | id string 22 | 23 | Enabled bool 24 | Identifier string 25 | 26 | Title string 27 | Content string 28 | 29 | CreatedAt time.Time 30 | UpdatedAt time.Time 31 | } 32 | 33 | // DefaultCMSPageCollection is a default implementer of InterfaceCMSPageCollection 34 | type DefaultCMSPageCollection struct { 35 | listCollection db.InterfaceDBCollection 36 | listExtraAtributes []string 37 | } 38 | -------------------------------------------------------------------------------- /app/actors/cms/page/page.i_listable.go: -------------------------------------------------------------------------------- 1 | package page 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/cms" 6 | ) 7 | 8 | // GetCollection returns collection of current instance type 9 | func (it *DefaultCMSPage) GetCollection() models.InterfaceCollection { 10 | model, _ := models.GetModel(cms.ConstModelNameCMSPageCollection) 11 | if result, ok := model.(cms.InterfaceCMSPageCollection); ok { 12 | return result 13 | } 14 | 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /app/actors/cms/page/page.i_model.go: -------------------------------------------------------------------------------- 1 | package page 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/cms" 6 | ) 7 | 8 | // GetModelName returns model name 9 | func (it *DefaultCMSPage) GetModelName() string { 10 | return cms.ConstModelNameCMSPage 11 | } 12 | 13 | // GetImplementationName returns model implementation name 14 | func (it *DefaultCMSPage) GetImplementationName() string { 15 | return "DefaultCMSPage" 16 | } 17 | 18 | // New returns new instance of model implementation object 19 | func (it *DefaultCMSPage) New() (models.InterfaceModel, error) { 20 | return &DefaultCMSPage{}, nil 21 | } 22 | -------------------------------------------------------------------------------- /app/actors/discount/coupon/config.go: -------------------------------------------------------------------------------- 1 | package coupon 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | ) 6 | 7 | // setupConfig setups package configuration values for a system 8 | func setupConfig() error { 9 | config := env.GetConfig() 10 | if config == nil { 11 | err := env.ErrorNew(ConstErrorModule, env.ConstErrorLevelStartStop, "6a991234-a2bb-454e-9d5b-a7c2da1cdeb1", "can't obtain config") 12 | return env.ErrorDispatch(err) 13 | } 14 | 15 | err := config.RegisterItem(env.StructConfigItem{ 16 | Path: ConstConfigPathDiscounts, 17 | Value: nil, 18 | Type: env.ConstConfigTypeGroup, 19 | Editor: "", 20 | Options: nil, 21 | Label: "Discounts", 22 | Description: "Discounts related options", 23 | Image: "", 24 | }, nil) 25 | 26 | if err != nil { 27 | return env.ErrorDispatch(err) 28 | } 29 | 30 | err = config.RegisterItem(env.StructConfigItem{ 31 | Path: ConstConfigPathDiscountApplyPriority, 32 | Value: 2.10, 33 | Type: env.ConstConfigTypeFloat, 34 | Editor: "line_text", 35 | Options: nil, 36 | Label: "Discounts calculating position", 37 | Description: "This value used for using position to calculate it's possible applicable amount (Subtotal - 1, Shipping - 2, Grand total - 3)", 38 | Image: "", 39 | }, nil) 40 | 41 | if err != nil { 42 | return env.ErrorDispatch(err) 43 | } 44 | 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /app/actors/discount/coupon/decl.go: -------------------------------------------------------------------------------- 1 | // Package coupon is a default implementation of discount interface declared in 2 | // "github.com/ottemo/commerce/app/models/checkout" package 3 | package coupon 4 | 5 | import ( 6 | "github.com/ottemo/commerce/env" 7 | ) 8 | 9 | // Package global constants 10 | const ( 11 | ConstSessionKeyCurrentRedemptions = "current_redemption_codes" 12 | ConstCollectionNameCouponDiscounts = "coupon_discounts" 13 | 14 | ConstConfigPathDiscounts = "general.discounts" 15 | ConstConfigPathDiscountApplyPriority = "general.discounts.discount_apply_priority" 16 | 17 | ConstErrorModule = "coupon" 18 | ConstErrorLevel = env.ConstErrorLevelActor 19 | ) 20 | 21 | // Coupon is a default implementer of InterfaceDiscount 22 | type Coupon struct{} 23 | 24 | // usedCoupons contains used coupon codes with visitorsId's, initialize from orders and updated on checkout success 25 | var usedCoupons map[string][]string 26 | 27 | type discount struct { 28 | Code string 29 | Name string 30 | Total float64 31 | Amount float64 32 | Percents float64 33 | Qty int 34 | } 35 | -------------------------------------------------------------------------------- /app/actors/discount/giftcard/decl.go: -------------------------------------------------------------------------------- 1 | // Package giftcard creates and manage gift cards 2 | package giftcard 3 | 4 | import ( 5 | "github.com/ottemo/commerce/env" 6 | ) 7 | 8 | // Package global constants 9 | const ( 10 | ConstSessionKeyAppliedGiftCardCodes = "applied_giftcard_codes" 11 | ConstCollectionNameGiftCard = "gift_card" 12 | 13 | ConstConfigPathGiftEmailTemplate = "general.discounts.giftCard_email" 14 | ConstConfigPathGiftEmailSubject = "general.discounts.giftCard_email_subject" 15 | ConstConfigPathGiftCardSKU = "general.discounts.giftCard_SKU_code" 16 | 17 | ConstConfigPathGiftCardApplyPriority = "general.discounts.giftCard_apply_priority" 18 | 19 | ConstConfigPathGiftCardAdminBuyerName = "general.discounts.giftCard_admin_buyer_name" 20 | ConstConfigPathGiftCardAdminBuyerEmail = "general.discounts.giftCard_admin_buyer_email" 21 | 22 | ConstErrorModule = "giftcard" 23 | ConstErrorLevel = env.ConstErrorLevelActor 24 | 25 | ConstGiftCardStatusNew = "new" 26 | ConstGiftCardStatusApplied = "applied" 27 | ConstGiftCardStatusUsed = "used" 28 | ConstGiftCardStatusOverCredited = "negative" 29 | ConstGiftCardStatusRefilled = "refilled" 30 | ConstGiftCardStatusCancelled = "cancelled" 31 | ConstGiftCardStatusDelivered = "delivered" 32 | ) 33 | 34 | // DefaultGiftcard is a default implementer of InterfaceDiscount 35 | type DefaultGiftcard struct{} 36 | 37 | // Shipping is a default free shipping rate for Gift Cards 38 | type Shipping struct{} 39 | -------------------------------------------------------------------------------- /app/actors/discount/giftcard/i_shipping_method.go: -------------------------------------------------------------------------------- 1 | package giftcard 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/ottemo/commerce/app/models/checkout" 7 | "github.com/ottemo/commerce/env" 8 | "github.com/ottemo/commerce/utils" 9 | ) 10 | 11 | // GetName returns name of shipping method 12 | func (it *Shipping) GetName() string { 13 | return "No Shipping" 14 | } 15 | 16 | // GetCode returns code of shipping method 17 | func (it *Shipping) GetCode() string { 18 | return "giftcards" 19 | } 20 | 21 | // IsAllowed checks for method applicability 22 | func (it *Shipping) IsAllowed(checkout checkout.InterfaceCheckout) bool { 23 | return true 24 | } 25 | 26 | // GetRates returns rates allowed by shipping method for a given checkout 27 | func (it *Shipping) GetRates(currentCheckout checkout.InterfaceCheckout) []checkout.StructShippingRate { 28 | 29 | result := []checkout.StructShippingRate{} 30 | 31 | giftCardSkuElement := utils.InterfaceToString(env.ConfigGetValue(ConstConfigPathGiftCardSKU)) 32 | 33 | if cart := currentCheckout.GetCart(); cart != nil { 34 | for _, cartItem := range cart.GetItems() { 35 | 36 | cartProduct := cartItem.GetProduct() 37 | if cartProduct == nil { 38 | continue 39 | } 40 | 41 | if err := cartProduct.ApplyOptions(cartItem.GetOptions()); err != nil { 42 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "5fcd3354-17b5-43ad-946b-0146a6e0017a", err.Error()) 43 | } 44 | if !strings.Contains(cartProduct.GetSku(), giftCardSkuElement) { 45 | return result 46 | } 47 | } 48 | } 49 | 50 | result = []checkout.StructShippingRate{ 51 | checkout.StructShippingRate{ 52 | Code: "freeshipping", 53 | Name: "GiftCards", 54 | Price: 0, 55 | }} 56 | 57 | return result 58 | } 59 | 60 | // GetAllRates returns all the shippmeng method rates available in the system. 61 | func (it Shipping) GetAllRates() []checkout.StructShippingRate { 62 | 63 | result := []checkout.StructShippingRate{ 64 | checkout.StructShippingRate{ 65 | Code: "freeshipping", 66 | Name: "GiftCards", 67 | Price: 0, 68 | }, 69 | } 70 | 71 | return result 72 | } 73 | -------------------------------------------------------------------------------- /app/actors/discount/saleprice/decl.go: -------------------------------------------------------------------------------- 1 | // Package saleprice is an implementation of discount interface declared in 2 | // "github.com/ottemo/commerce/app/models/checkout" package 3 | package saleprice 4 | 5 | import ( 6 | "time" 7 | 8 | "github.com/ottemo/commerce/db" 9 | "github.com/ottemo/commerce/env" 10 | 11 | "github.com/ottemo/commerce/app/models" 12 | "github.com/ottemo/commerce/app/models/discount/saleprice" 13 | "github.com/ottemo/commerce/app/models/product" 14 | ) 15 | 16 | // Package global constants 17 | const ( 18 | ConstConfigPathGroup = "general.sale_price" 19 | ConstConfigPathEnabled = "general.sale_price.enabled" 20 | ConstConfigPathSalePriceApplyPriority = "general.sale_price.priority" 21 | 22 | ConstErrorModule = "saleprice" 23 | ConstErrorLevel = env.ConstErrorLevelActor 24 | ) 25 | 26 | // DefaultSalePrice is an implementer of InterfaceDiscount 27 | type DefaultSalePrice struct { 28 | id string 29 | 30 | amount float64 31 | endDatetime time.Time 32 | productID string 33 | startDatetime time.Time 34 | } 35 | 36 | // DefaultSalePriceCollection is a default implementer of InterfaceSalePriceCollection 37 | type DefaultSalePriceCollection struct { 38 | listCollection db.InterfaceDBCollection 39 | listExtraAtributes []string 40 | } 41 | 42 | // SalePriceDelegate type implements InterfaceAttributesDelegate and have handles 43 | // on InterfaceStorable methods which should have call-back on model method call 44 | // in order to test it we are pushing the callback status to model instance 45 | type SalePriceDelegate struct { 46 | productInstance product.InterfaceProduct 47 | SalePrices []saleprice.InterfaceSalePrice 48 | } 49 | 50 | // salePriceDelegate variable that is currently used as a sale price delegate to extend product attributes 51 | var salePriceDelegate models.InterfaceAttributesDelegate 52 | -------------------------------------------------------------------------------- /app/actors/order/item_collection.i_model.go: -------------------------------------------------------------------------------- 1 | package order 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/order" 6 | "github.com/ottemo/commerce/db" 7 | "github.com/ottemo/commerce/env" 8 | ) 9 | 10 | // GetModelName returns model name 11 | func (it *DefaultOrderItemCollection) GetModelName() string { 12 | return order.ConstModelNameOrderItemCollection 13 | } 14 | 15 | // GetImplementationName returns model implementation name 16 | func (it *DefaultOrderItemCollection) GetImplementationName() string { 17 | return "Default" + order.ConstModelNameOrderItemCollection 18 | } 19 | 20 | // New returns new instance of model implementation object 21 | func (it *DefaultOrderItemCollection) New() (models.InterfaceModel, error) { 22 | dbCollection, err := db.GetCollection(ConstCollectionNameOrderItems) 23 | if err != nil { 24 | return nil, env.ErrorDispatch(err) 25 | } 26 | 27 | return &DefaultOrderItemCollection{listCollection: dbCollection, listExtraAtributes: make([]string, 0)}, nil 28 | } 29 | -------------------------------------------------------------------------------- /app/actors/order/item_collection.i_order_item_collection.go: -------------------------------------------------------------------------------- 1 | package order 2 | 3 | import ( 4 | "github.com/ottemo/commerce/db" 5 | ) 6 | 7 | // GetDBCollection returns database collection 8 | func (it *DefaultOrderItemCollection) GetDBCollection() db.InterfaceDBCollection { 9 | return it.listCollection 10 | } 11 | -------------------------------------------------------------------------------- /app/actors/order/order.i_listable.go: -------------------------------------------------------------------------------- 1 | package order 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/order" 6 | ) 7 | 8 | // GetCollection returns collection of current instance type 9 | func (it *DefaultOrder) GetCollection() models.InterfaceCollection { 10 | model, _ := models.GetModel(order.ConstModelNameOrderCollection) 11 | if result, ok := model.(order.InterfaceOrderCollection); ok { 12 | return result 13 | } 14 | 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /app/actors/order/order.i_model.go: -------------------------------------------------------------------------------- 1 | package order 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/order" 6 | ) 7 | 8 | // GetModelName returns model name we have implementation for 9 | func (it *DefaultOrder) GetModelName() string { 10 | return order.ConstModelNameOrder 11 | } 12 | 13 | // GetImplementationName returns name of current model implementation 14 | func (it *DefaultOrder) GetImplementationName() string { 15 | return "Default" + order.ConstModelNameOrder 16 | } 17 | 18 | // New makes new instance of model 19 | func (it *DefaultOrder) New() (models.InterfaceModel, error) { 20 | return &DefaultOrder{Items: make(map[int]order.InterfaceOrderItem)}, nil 21 | } 22 | -------------------------------------------------------------------------------- /app/actors/order/order_collection.i_model.go: -------------------------------------------------------------------------------- 1 | package order 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/order" 6 | "github.com/ottemo/commerce/db" 7 | "github.com/ottemo/commerce/env" 8 | ) 9 | 10 | // GetModelName returns model name 11 | func (it *DefaultOrderCollection) GetModelName() string { 12 | return order.ConstModelNameOrderCollection 13 | } 14 | 15 | // GetImplementationName returns model implementation name 16 | func (it *DefaultOrderCollection) GetImplementationName() string { 17 | return "Default" + order.ConstModelNameOrderCollection 18 | } 19 | 20 | // New returns new instance of model implementation object 21 | func (it *DefaultOrderCollection) New() (models.InterfaceModel, error) { 22 | dbCollection, err := db.GetCollection(ConstCollectionNameOrder) 23 | if err != nil { 24 | return nil, env.ErrorDispatch(err) 25 | } 26 | 27 | return &DefaultOrderCollection{listCollection: dbCollection, listExtraAtributes: make([]string, 0)}, nil 28 | } 29 | -------------------------------------------------------------------------------- /app/actors/order/order_collection.i_order_collection.go: -------------------------------------------------------------------------------- 1 | package order 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models/order" 5 | "github.com/ottemo/commerce/db" 6 | "github.com/ottemo/commerce/env" 7 | ) 8 | 9 | // GetDBCollection returns database collection 10 | func (it *DefaultOrderCollection) GetDBCollection() db.InterfaceDBCollection { 11 | return it.listCollection 12 | } 13 | 14 | // ListOrders returns array of products in model instance form 15 | func (it *DefaultOrderCollection) ListOrders() []order.InterfaceOrder { 16 | var result []order.InterfaceOrder 17 | 18 | dbRecords, err := it.listCollection.Load() 19 | if err != nil { 20 | return result 21 | } 22 | 23 | for _, dbRecordData := range dbRecords { 24 | orderModel, err := order.GetOrderModel() 25 | if err != nil { 26 | return result 27 | } 28 | if err := orderModel.FromHashMap(dbRecordData); err != nil { 29 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "29635564-a788-46d7-9485-f286ae481533", err.Error()) 30 | } 31 | 32 | result = append(result, orderModel) 33 | } 34 | 35 | return result 36 | } 37 | -------------------------------------------------------------------------------- /app/actors/other/emma/api.go: -------------------------------------------------------------------------------- 1 | package emma 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | "github.com/ottemo/commerce/env" 6 | "github.com/ottemo/commerce/utils" 7 | ) 8 | 9 | // setupAPI setups package related API endpoint routines 10 | func setupAPI() error { 11 | 12 | service := api.GetRestService() 13 | 14 | // Public 15 | service.POST("emma/contact", APIEmmaAddContact) 16 | 17 | return nil 18 | } 19 | 20 | // APIEmmaAddContact - return message, after add contact 21 | // - email should be specified in "email" argument 22 | func APIEmmaAddContact(context api.InterfaceApplicationContext) (interface{}, error) { 23 | 24 | // check request context 25 | //--------------------- 26 | requestData, err := api.GetRequestContentAsMap(context) 27 | if err != nil { 28 | return nil, env.ErrorDispatch(err) 29 | } 30 | 31 | if !utils.KeysInMapAndNotBlank(requestData, "email") { 32 | context.SetResponseStatusBadRequest() 33 | return nil, env.ErrorNew(ConstErrorModule, ConstErrorLevel, "6372b9a3-29f3-4ea4-a19f-40051a8f330b", "email has not been specified") 34 | } 35 | email := utils.InterfaceToString(requestData["email"]) 36 | 37 | if !utils.KeysInMapAndNotBlank(requestData, "group_ids") { 38 | context.SetResponseStatusBadRequest() 39 | return nil, env.ErrorNew(ConstErrorModule, ConstErrorLevel, "eee81283-86c4-487c-a5b5-b78996be038e", "group_ids not specified") 40 | } 41 | groupIDs := utils.InterfaceToString(requestData["group_ids"]) 42 | 43 | if !utils.ValidEmailAddress(email) { 44 | context.SetResponseStatusBadRequest() 45 | return nil, env.ErrorNew(ConstErrorModule, ConstErrorLevel, "b54b0917-acc0-469f-925e-8f85a1feac7b", "The email address, "+email+", is not in valid format.") 46 | } 47 | 48 | result, err := subscribe(email, groupIDs) 49 | if err != nil { 50 | context.SetResponseStatusInternalServerError() 51 | return nil, env.ErrorDispatch(err) 52 | } 53 | 54 | return result, nil 55 | } 56 | -------------------------------------------------------------------------------- /app/actors/other/emma/decl.go: -------------------------------------------------------------------------------- 1 | package emma 2 | 3 | import "github.com/ottemo/commerce/env" 4 | 5 | // Package constants for Emma module 6 | const ( 7 | ConstErrorModule = "emma" 8 | ConstErrorLevel = env.ConstErrorLevelAPI 9 | 10 | ConstConfigPathEmma = "general.emma" 11 | ConstConfigPathEmmaEnabled = "general.emma.enabled" 12 | ConstConfigPathEmmaPublicAPIKey = "general.emma.public_api_key" 13 | ConstConfigPathEmmaPrivateAPIKey = "general.emma.private_api_key" 14 | ConstConfigPathEmmaAccountID = "general.emma.account_id" 15 | ConstConfigPathEmmaSKU = "general.emma.trigger_sku" 16 | ConstConfigPathEmmaDefaultGroupIds = "general.emma.default_group_ids" 17 | ) 18 | 19 | var ( 20 | emmaService emmaServiceType 21 | ) 22 | 23 | type emmaCredentialsGetter interface { 24 | get() (emmaCredentialsType, error) 25 | } 26 | -------------------------------------------------------------------------------- /app/actors/other/emma/init.go: -------------------------------------------------------------------------------- 1 | package emma 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | "github.com/ottemo/commerce/app" 6 | "github.com/ottemo/commerce/env" 7 | ) 8 | 9 | func init() { 10 | app.OnAppStart(appStart) 11 | env.RegisterOnConfigStart(setupConfig) 12 | api.RegisterOnRestServiceStart(setupAPI) 13 | } 14 | 15 | func appStart() error { 16 | env.EventRegisterListener("checkout.success", checkoutSuccessHandler) 17 | 18 | emmaService = *newEmmaService() 19 | 20 | return nil 21 | } 22 | -------------------------------------------------------------------------------- /app/actors/other/friendmail/config.go: -------------------------------------------------------------------------------- 1 | package friendmail 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | ) 6 | 7 | // setupConfig setups package configuration values for a system 8 | func setupConfig() error { 9 | if config := env.GetConfig(); config != nil { 10 | err := config.RegisterItem(env.StructConfigItem{ 11 | Path: ConstConfigPathFriendMail, 12 | Value: nil, 13 | Type: env.ConstConfigTypeGroup, 14 | Editor: "", 15 | Options: nil, 16 | Label: "Refer-A-Friend", 17 | Description: "Referal program", 18 | Image: "", 19 | }, nil) 20 | 21 | if err != nil { 22 | return env.ErrorDispatch(err) 23 | } 24 | 25 | err = config.RegisterItem(env.StructConfigItem{ 26 | Path: ConstConfigPathFriendMailEmailSubject, 27 | Value: "Email friend", 28 | Type: env.ConstConfigTypeVarchar, 29 | Editor: "line_text", 30 | Options: nil, 31 | Label: "Email subject", 32 | Description: "Email subject for the friend form", 33 | Image: "", 34 | }, nil) 35 | 36 | if err != nil { 37 | return env.ErrorDispatch(err) 38 | } 39 | 40 | err = config.RegisterItem(env.StructConfigItem{ 41 | Path: ConstConfigPathFriendMailEmailTemplate, 42 | Value: `Dear {{.friend_name}} 43 |
44 |
45 | Your friend sent you an email: 46 | {{.content}}`, 47 | Type: env.ConstConfigTypeHTML, 48 | Editor: "multiline_text", 49 | Options: nil, 50 | Label: "Email Body", 51 | Description: "Email body template for the friend form", 52 | Image: "", 53 | }, nil) 54 | 55 | if err != nil { 56 | return env.ErrorDispatch(err) 57 | } 58 | } else { 59 | err := env.ErrorNew(ConstErrorModule, env.ConstErrorLevelStartStop, "81e49a2f-906d-40ed-9a47-bb2b9c5e8f40", "Unable to obtain configuration for Friend Mail") 60 | return env.ErrorDispatch(err) 61 | } 62 | 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /app/actors/other/friendmail/decl.go: -------------------------------------------------------------------------------- 1 | // Package friendmail is an extension which provides ability to send email to friend 2 | package friendmail 3 | 4 | import ( 5 | "github.com/dchest/captcha" 6 | "github.com/ottemo/commerce/env" 7 | "sync" 8 | "time" 9 | ) 10 | 11 | // Package global constants 12 | const ( 13 | ConstCollectionNameFriendMail = "friend_mail" 14 | 15 | ConstConfigPathFriendMail = "general.friendmail" 16 | ConstConfigPathFriendMailEmailTemplate = "general.friendmail.template" 17 | ConstConfigPathFriendMailEmailSubject = "general.friendmail.subject" 18 | 19 | ConstErrorModule = "friendmail" 20 | ConstErrorLevel = env.ConstErrorLevelActor 21 | 22 | ConstMaxCaptchaItems = 100000 // captcha maximum amount (to prevent memory leaks) 23 | ConstCaptchaLifeTime = 300 // seconds generated captcha works (5 min) 24 | ) 25 | 26 | var ( 27 | captchaValuesMutex sync.RWMutex // synchronization on captchaValues variable 28 | captchaValues map[string]time.Time // global variable to track generated captcha codes 29 | captchaStore captcha.Store 30 | ) 31 | -------------------------------------------------------------------------------- /app/actors/other/friendmail/init.go: -------------------------------------------------------------------------------- 1 | package friendmail 2 | 3 | import ( 4 | "github.com/dchest/captcha" 5 | "github.com/ottemo/commerce/api" 6 | "github.com/ottemo/commerce/db" 7 | "github.com/ottemo/commerce/env" 8 | "time" 9 | ) 10 | 11 | // init makes package self-initialization routine 12 | func init() { 13 | db.RegisterOnDatabaseStart(setupDB) 14 | env.RegisterOnConfigStart(setupConfig) 15 | api.RegisterOnRestServiceStart(setupAPI) 16 | 17 | captchaValues = make(map[string]time.Time) 18 | 19 | captchaStore = captcha.NewMemoryStore(ConstMaxCaptchaItems, time.Second*ConstCaptchaLifeTime) 20 | captcha.SetCustomStore(captchaStore) 21 | 22 | // starting timer for garbage collector 23 | if ConstCaptchaLifeTime > 0 { 24 | timerInterval := time.Second * ConstCaptchaLifeTime 25 | ticker := time.NewTicker(timerInterval) 26 | go func() { 27 | for _ = range ticker.C { 28 | captchaValuesMutex.Lock() 29 | 30 | currentTime := time.Now() 31 | for key, value := range captchaValues { 32 | if currentTime.Sub(value).Seconds() >= ConstCaptchaLifeTime { 33 | captchaStore.Get(key, true) 34 | delete(captchaValues, key) 35 | } 36 | } 37 | 38 | captchaValuesMutex.Unlock() 39 | } 40 | }() 41 | } 42 | } 43 | 44 | // setupDB prepares system database for package usage 45 | func setupDB() error { 46 | 47 | if collection, err := db.GetCollection(ConstCollectionNameFriendMail); err == nil { 48 | if err := collection.AddColumn("date", db.ConstTypeID, true); err != nil { 49 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "52acc483-0132-4252-acf3-9e965eefd73b", err.Error()) 50 | } 51 | if err := collection.AddColumn("email", db.ConstTypeVarchar, true); err != nil { 52 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "53288f58-6cc8-4ae5-913d-d8f735a66678", err.Error()) 53 | } 54 | if err := collection.AddColumn("data", db.ConstTypeJSON, false); err != nil { 55 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "c18bbf88-a104-4154-b8eb-0ccb6ae7a7e9", err.Error()) 56 | } 57 | } else { 58 | return env.ErrorDispatch(err) 59 | } 60 | 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /app/actors/other/grouping/decl.go: -------------------------------------------------------------------------------- 1 | // Package grouping implements products set grouping into another set 2 | package grouping 3 | 4 | import ( 5 | "github.com/ottemo/commerce/env" 6 | ) 7 | 8 | // Package global constants 9 | const ( 10 | ConstErrorModule = "grouping" 11 | ConstErrorLevel = env.ConstErrorLevelActor 12 | 13 | ConstGroupingConfigPath = "general.stock.grouprules" 14 | ) 15 | 16 | // Package global variables 17 | var ( 18 | currentRules = make([]interface{}, 0) 19 | ) 20 | -------------------------------------------------------------------------------- /app/actors/other/grouping/init.go: -------------------------------------------------------------------------------- 1 | package grouping 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app" 5 | "github.com/ottemo/commerce/env" 6 | "github.com/ottemo/commerce/utils" 7 | ) 8 | 9 | // init makes package self-initialization routine before app start 10 | func init() { 11 | app.OnAppStart(onAppStart) 12 | env.RegisterOnConfigStart(setupConfig) 13 | } 14 | 15 | // onAppStart makes module initialization on application startup 16 | func onAppStart() error { 17 | 18 | rules, err := utils.DecodeJSONToArray(env.ConfigGetValue(ConstGroupingConfigPath)) 19 | if err != nil { 20 | rules = make([]interface{}, 0) 21 | } 22 | currentRules = rules 23 | 24 | env.EventRegisterListener("api.cart.update", updateCartHandler) 25 | 26 | return nil 27 | } 28 | -------------------------------------------------------------------------------- /app/actors/other/mailchimp/decl.go: -------------------------------------------------------------------------------- 1 | package mailchimp 2 | 3 | import "github.com/ottemo/commerce/env" 4 | 5 | // Package constants for Mailchimp module 6 | const ( 7 | ConstErrorModule = "mailchimp" 8 | ConstErrorLevel = env.ConstErrorLevelActor 9 | 10 | ConstMailchimpSubscribeStatus = "subscribed" 11 | 12 | ConstConfigPathMailchimp = "general.mailchimp" 13 | ConstConfigPathMailchimpEnabled = "general.mailchimp.enabled" 14 | ConstConfigPathMailchimpAPIKey = "general.mailchimp.api_key" 15 | ConstConfigPathMailchimpBaseURL = "general.mailchimp.base_url" 16 | ConstConfigPathMailchimpSupportAddress = "general.mailchimp.support_addr" 17 | ConstConfigPathMailchimpEmailTemplate = "general.mailchimp.template" 18 | ConstConfigPathMailchimpSubjectLine = "general.mailchimp.subject_line" 19 | ConstConfigPathMailchimpList = "general.mailchimp.subscribe_to_list" 20 | ConstConfigPathMailchimpSKU = "general.mailchimp.trigger_sku" 21 | ) 22 | 23 | // Registration is a struct to hold a single registation for a Mailchimp mailing list. 24 | type Registration struct { 25 | EmailAddress string `json:"email_address"` 26 | Status string `json:"status"` 27 | MergeFields map[string]string `json:"merge_fields"` 28 | } 29 | -------------------------------------------------------------------------------- /app/actors/other/mailchimp/init.go: -------------------------------------------------------------------------------- 1 | package mailchimp 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app" 5 | "github.com/ottemo/commerce/env" 6 | ) 7 | 8 | func init() { 9 | app.OnAppStart(appStart) 10 | env.RegisterOnConfigStart(setupConfig) 11 | } 12 | 13 | func appStart() error { 14 | env.EventRegisterListener("checkout.success", checkoutSuccessHandler) 15 | 16 | return nil 17 | } 18 | -------------------------------------------------------------------------------- /app/actors/other/mailchimp/mailchimp_test.go: -------------------------------------------------------------------------------- 1 | package mailchimp_test 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "testing" 7 | "time" 8 | 9 | "github.com/ottemo/commerce/app/actors/other/mailchimp" 10 | "github.com/ottemo/commerce/env" 11 | "github.com/ottemo/commerce/test" 12 | "github.com/ottemo/commerce/db" 13 | ) 14 | 15 | func TestMailchimpSubscribe(t *testing.T) { 16 | // start app 17 | err := test.StartAppInTestingMode() 18 | if err != nil { 19 | t.Error(err) 20 | } 21 | 22 | db.RegisterOnDatabaseStart(func () error { 23 | testMailchimpSubscribe(t) 24 | return nil 25 | }) 26 | } 27 | 28 | func testMailchimpSubscribe(tst *testing.T) { 29 | //set the configuration to allow mailchimp 30 | var config = env.GetConfig() 31 | if err := config.SetValue(mailchimp.ConstConfigPathMailchimpEnabled, true); err != nil { 32 | tst.Error(err) 33 | } 34 | if err := config.SetValue(mailchimp.ConstConfigPathMailchimpAPIKey, "23dbf42618e8f43e624a6dd89de9bd46-us12"); err != nil { 35 | tst.Error(err) 36 | } 37 | if err := config.SetValue(mailchimp.ConstConfigPathMailchimpBaseURL, "https://us12.api.mailchimp.com/3.0/"); err != nil { 38 | tst.Error(err) 39 | } 40 | 41 | rand.Seed(time.Now().UTC().UnixNano()) 42 | testRegistration := mailchimp.Registration{ 43 | EmailAddress: fmt.Sprintf("test+%d@myottemotest.com", rand.Int()), 44 | Status: "subscribed", 45 | MergeFields: map[string]string{ 46 | "FNAME": "Test", 47 | "LNAME": "User", 48 | }, 49 | } 50 | 51 | if err := mailchimp.Subscribe("b9537d1e65", testRegistration); err != nil { 52 | tst.Error(err) 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/actors/other/shipstation/decl.go: -------------------------------------------------------------------------------- 1 | package shipstation 2 | 3 | import "github.com/ottemo/commerce/env" 4 | 5 | const ( 6 | ConstErrorModule = "shipstation" 7 | ConstErrorLevel = env.ConstErrorLevel 8 | ) 9 | 10 | // struct goes here 11 | type Orders struct { 12 | Orders []Order `xml:"Order"` 13 | } 14 | 15 | type Order struct { 16 | OrderId string `xml:"OrderID"` 17 | OrderNumber string 18 | OrderDate string // Set to string because we can't control the date formatting otherwise 19 | OrderStatus string 20 | LastModified string // Same formatting issue 21 | TaxAmount float64 22 | ShippingAmount float64 23 | OrderTotal float64 24 | 25 | // Containers 26 | Customer Customer 27 | Items []OrderItem `xml:"Items>Item"` 28 | } 29 | 30 | type Customer struct { 31 | CustomerCode string // We use email address here 32 | 33 | // Containers 34 | BillingAddress BillingAddress `xml:"BillTo"` 35 | ShippingAddress ShippingAddress `xml:"ShipTo"` 36 | } 37 | 38 | type BillingAddress struct { 39 | Name string 40 | } 41 | 42 | type ShippingAddress struct { 43 | Name string 44 | Address1 string 45 | City string 46 | State string 47 | PostalCode string 48 | Country string 49 | } 50 | 51 | type OrderItem struct { 52 | Sku string `xml:"SKU"` 53 | Name string 54 | Quantity int 55 | UnitPrice float64 56 | Adjustment bool 57 | } 58 | -------------------------------------------------------------------------------- /app/actors/other/shipstation/init.go: -------------------------------------------------------------------------------- 1 | package shipstation 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | "github.com/ottemo/commerce/env" 6 | ) 7 | 8 | func init() { 9 | env.RegisterOnConfigStart(setupConfig) 10 | api.RegisterOnRestServiceStart(setupAPI) 11 | } 12 | -------------------------------------------------------------------------------- /app/actors/other/shipstation/internal.go: -------------------------------------------------------------------------------- 1 | package shipstation 2 | 3 | import "github.com/ottemo/commerce/env" 4 | 5 | // buildTrackingUrl Assemble a tracking url from the carrier and tracking number 6 | // - http://verysimple.com/2011/07/06/ups-tracking-url/ 7 | // - http://eshipguy.com/tracking/ 8 | func buildTrackingUrl(carrier string, trackingNumber string) string { 9 | var trackingUrl string 10 | 11 | // These are the recognized carriers that shipstation might pass back 12 | trackingMap := map[string]string{ 13 | "AccessWorldwide": "", 14 | "APC": "", 15 | "Asendia": "", 16 | "AustraliaPost": "", 17 | "BrokersWorldWide": "", 18 | "CanadaPost": "", 19 | "DHL": "http://track.dhl-usa.com/TrackByNbr.asp?ShipmentNumber=", 20 | "DHLCanada": "", 21 | "DHLGlobalMail": "", 22 | "FedEx": "http://www.fedex.com/Tracking?action=track&tracknumbers=", 23 | "FedExCanada": "", 24 | "FedExInternationalMailService": "", 25 | "FirstMile": "", 26 | "Globegistics": "", 27 | "IMEX": "", 28 | "LoneStar": "", 29 | "Newgistics": "", 30 | "OnTrac": "", 31 | "Other": "", 32 | "RoyalMail": "", 33 | "UPS": "http://wwwapps.ups.com/WebTracking/track?track=yes&trackNums=", 34 | "UPSMI": "https://www.ups-mi.net/packageID/packageid.aspx?pid=", 35 | "USPS": "https://tools.usps.com/go/TrackConfirmAction_input?qtc_tLabels1=", 36 | } 37 | 38 | trackingUrl = trackingMap[carrier] 39 | if trackingUrl == "" { 40 | env.LogError(env.ErrorNew(ConstErrorModule, ConstErrorLevel, "1799137F-0FAD-4200-BAFF-954C30FCE674", "We don't have a tracking url set up for a certain carrier: "+carrier)) 41 | } else { 42 | trackingUrl += trackingNumber 43 | } 44 | 45 | return trackingUrl 46 | } 47 | -------------------------------------------------------------------------------- /app/actors/other/trustpilot/decl.go: -------------------------------------------------------------------------------- 1 | // Package trustpilot implements trust pilot functions 2 | package trustpilot 3 | 4 | import ( 5 | "time" 6 | ) 7 | 8 | // Package global constants 9 | const ( 10 | ConstEmailSubject = "Purchase feedback" 11 | 12 | ConstErrorModule = "trustpilot" 13 | ConstErrorLevel = 1 // if i tell you to log, then do it 14 | 15 | ConstOrderCustomInfoLinkKey = "trustpilot_link" 16 | ConstOrderCustomInfoSentKey = "trustpilot_sent" 17 | 18 | ConstConfigPathTrustPilot = "general.trustpilot" 19 | ConstConfigPathTrustPilotEnabled = "general.trustpilot.enabled" 20 | ConstConfigPathTrustPilotAPIKey = "general.trustpilot.apiKey" 21 | ConstConfigPathTrustPilotAPISecret = "general.trustpilot.apiSecret" 22 | ConstConfigPathTrustPilotBusinessUnitID = "general.trustpilot.businessUnitID" 23 | ConstConfigPathTrustPilotUsername = "general.trustpilot.username" 24 | ConstConfigPathTrustPilotPassword = "general.trustpilot.password" 25 | ConstConfigPathTrustPilotEmailTemplate = "general.trustpilot.emailTemplate" 26 | ConstConfigPathTrustPilotProductBrand = "general.trustpilot.productBrand" 27 | 28 | ConstRatingSummaryURL = "https://api.trustpilot.com/v1/private/product-reviews/business-units/{businessUnitId}/summaries" 29 | ) 30 | 31 | // TODO: we should use some caching module instead of just global variables 32 | // Package global variables 33 | var ( 34 | lastTimeSummariesUpdate time.Time 35 | summariesCache interface{} 36 | ) 37 | -------------------------------------------------------------------------------- /app/actors/other/vantagepoint/actors/decl.go: -------------------------------------------------------------------------------- 1 | package actors 2 | 3 | import "github.com/ottemo/commerce/env" 4 | 5 | const ( 6 | ConstErrorModule = "vantagepoint" 7 | ConstErrorLevel = env.ConstErrorLevelActor 8 | ) 9 | 10 | -------------------------------------------------------------------------------- /app/actors/other/vantagepoint/decl.go: -------------------------------------------------------------------------------- 1 | package vantagepoint 2 | 3 | import "github.com/ottemo/commerce/env" 4 | 5 | const ( 6 | ConstErrorModule = "vantagepoint" 7 | ConstErrorLevel = env.ConstErrorLevelActor 8 | ConstLogStorage = "vantagepoint.log" 9 | 10 | ConstConfigPathVantagePoint = "general.vantagepoint" 11 | ConstConfigPathVantagePointScheduleEnabled = "general.vantagepoint.schedule.enabled" 12 | ConstConfigPathVantagePointScheduleHour = "general.vantagepoint.schedule.hour" 13 | ConstConfigPathVantagePointUploadPath = "general.vantagepoint.upload.path" 14 | ConstConfigPathVantagePointUploadFileMask = "general.vantagepoint.upload.filemask" 15 | 16 | ConstSchedulerTaskName = "vantagePointCheckNewUploads" 17 | ) 18 | 19 | type UploadProcessorInterface interface { 20 | Process() error 21 | } 22 | -------------------------------------------------------------------------------- /app/actors/other/vantagepoint/init.go: -------------------------------------------------------------------------------- 1 | package vantagepoint 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app" 5 | "github.com/ottemo/commerce/db" 6 | "github.com/ottemo/commerce/env" 7 | ) 8 | 9 | func init() { 10 | initHoursList() 11 | 12 | db.RegisterOnDatabaseStart(onDatabaseStart) 13 | 14 | env.RegisterOnConfigStart(setupConfig) 15 | } 16 | 17 | func onDatabaseStart() error { 18 | app.OnAppStart(onAppStart) 19 | 20 | return nil 21 | } 22 | 23 | func onAppStart() error { 24 | if err := scheduleCheckNewUploads(); err != nil { 25 | _ = env.ErrorDispatch(err) 26 | } 27 | 28 | return nil 29 | } 30 | -------------------------------------------------------------------------------- /app/actors/payment/authorizenet/init.go: -------------------------------------------------------------------------------- 1 | package authorizenet 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | "github.com/ottemo/commerce/env" 6 | 7 | "github.com/ottemo/commerce/app/models/checkout" 8 | ) 9 | 10 | // init makes package self-initialization routine 11 | func init() { 12 | if err := checkout.RegisterPaymentMethod(new(DirectPostMethod)); err != nil { 13 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "c36db383-87db-45be-9cab-ce5c0967686c", err.Error()) 14 | } 15 | if err := checkout.RegisterPaymentMethod(new(RestMethod)); err != nil { 16 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "c7d65f90-7ba4-48b9-96b9-31e9d4cd49d0", err.Error()) 17 | } 18 | api.RegisterOnRestServiceStart(setupAPI) 19 | env.RegisterOnConfigStart(setupConfig) 20 | } 21 | -------------------------------------------------------------------------------- /app/actors/payment/braintree/decl.go: -------------------------------------------------------------------------------- 1 | // Package braintree is a "braintree payments" implementation of payment method interface declared in 2 | // "github.com/ottemo/commerce/app/models/checkout" package 3 | package braintree 4 | 5 | import ( 6 | "github.com/lionelbarrow/braintree-go" 7 | 8 | "github.com/ottemo/commerce/env" 9 | ) 10 | 11 | // Package global constants 12 | const ( 13 | // -------------------------------------- 14 | // Because of multiple payment modules supported by Braintree constant names and values are divided into 15 | // General - overall values 16 | // Method - specific per method values 17 | // 18 | // Note: group name is prefix of elements grouped in frontend 19 | 20 | // -------------------------------------- 21 | // General 22 | 23 | ConstGeneralConfigPathGroup = "payment.braintreeGeneral" 24 | 25 | ConstGeneralConfigPathEnabled = "payment.braintreeGeneral.enabled" 26 | ConstGeneralMethodConfigPathName = "payment.braintreeGeneral.name" // User customized name of the payment method 27 | 28 | ConstGeneralConfigPathEnvironment = "payment.braintreeGeneral.environment" 29 | ConstGeneralConfigPathMerchantID = "payment.braintreeGeneral.merchantID" 30 | ConstGeneralConfigPathPublicKey = "payment.braintreeGeneral.publicKey" 31 | ConstGeneralConfigPathPrivateKey = "payment.braintreeGeneral.privateKey" 32 | 33 | 34 | ConstEnvironmentSandbox = string(braintree.Sandbox) 35 | ConstEnvironmentProduction = string(braintree.Production) 36 | 37 | ConstErrorModule = "payment/braintree" 38 | ConstErrorLevel = env.ConstErrorLevelActor 39 | 40 | constLogStorage = "braintree.log" 41 | 42 | 43 | constCCMethodCode = "braintree" // Method code used in business logic 44 | constCCMethodInternalName = "Braintree Credit Card" // Human readable name of payment method 45 | 46 | ) 47 | 48 | // CreditCardMethod is a implementer of InterfacePaymentMethod for a Credit Card payment method 49 | type CreditCardMethod struct{} 50 | -------------------------------------------------------------------------------- /app/actors/payment/braintree/init.go: -------------------------------------------------------------------------------- 1 | package braintree 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | 6 | "github.com/ottemo/commerce/app/models/checkout" 7 | ) 8 | 9 | // init makes package self-initialization routine 10 | func init() { 11 | if err := checkout.RegisterPaymentMethod(new(CreditCardMethod)); err != nil { 12 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "194b26f7-3399-4121-80f1-e24305708871", err.Error()) 13 | } 14 | env.RegisterOnConfigStart(setupConfig) 15 | } 16 | -------------------------------------------------------------------------------- /app/actors/payment/checkmo/decl.go: -------------------------------------------------------------------------------- 1 | // Package checkmo is a "Check Money Order" implementation of payment method interface declared in 2 | // "github.com/ottemo/commerce/app/models/checkout" package 3 | package checkmo 4 | 5 | import ( 6 | "github.com/ottemo/commerce/env" 7 | ) 8 | 9 | // Package global constants 10 | const ( 11 | ConstPaymentCode = "checkmo" 12 | ConstPaymentName = "Check/Money Order" 13 | 14 | ConstConfigPathGroup = "payment.checkmo" 15 | ConstConfigPathEnabled = "payment.checkmo.enabled" 16 | ConstConfigPathTitle = "payment.checkmo.title" 17 | 18 | ConstErrorModule = "payment/checkmo" 19 | ConstErrorLevel = env.ConstErrorLevelActor 20 | ) 21 | 22 | // CheckMoneyOrder is a simplest implementer of InterfacePaymentMethod 23 | type CheckMoneyOrder struct{} 24 | -------------------------------------------------------------------------------- /app/actors/payment/checkmo/init.go: -------------------------------------------------------------------------------- 1 | package checkmo 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | 6 | "github.com/ottemo/commerce/app/models/checkout" 7 | ) 8 | 9 | // init makes package self-initialization routine 10 | func init() { 11 | if err := checkout.RegisterPaymentMethod(new(CheckMoneyOrder)); err != nil { 12 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "aa6367b6-3bf2-4843-915b-be8fbe3ffa6a", err.Error()) 13 | } 14 | env.RegisterOnConfigStart(setupConfig) 15 | } 16 | -------------------------------------------------------------------------------- /app/actors/payment/paypal/init.go: -------------------------------------------------------------------------------- 1 | package paypal 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | "github.com/ottemo/commerce/env" 6 | 7 | "github.com/ottemo/commerce/app/models/checkout" 8 | ) 9 | 10 | // init makes package self-initialization routine 11 | func init() { 12 | if err := checkout.RegisterPaymentMethod(new(Express)); err != nil { 13 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "4f72b52a-9af1-4725-a1dc-6731774be323", err.Error()) 14 | } 15 | if err := checkout.RegisterPaymentMethod(new(PayFlowAPI)); err != nil { 16 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "4abc8432-31e5-4ef7-bcdc-362b4b32fa8a", err.Error()) 17 | } 18 | api.RegisterOnRestServiceStart(setupAPI) 19 | env.RegisterOnConfigStart(setupConfig) 20 | } 21 | -------------------------------------------------------------------------------- /app/actors/payment/paypal/internal.go: -------------------------------------------------------------------------------- 1 | package paypal 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | ) 6 | 7 | // getCreditCardName returns credit card valid name 8 | func getCreditCardName(creditCardType string) string { 9 | 10 | switch creditCardType { 11 | case "0", "VI", "Visa", "VISA": 12 | return "VISA" 13 | 14 | case "1", "MC", "Master Card", "MasterCard": 15 | return "MasterCard" 16 | 17 | case "2", "DS", "Discover": 18 | return "Discover" 19 | 20 | case "3", "AX", "AE", "AmericanExpress", "American Express": 21 | return "AmericanExpress" 22 | 23 | case "4", "DC", "Diner’s Club", "Diner’sClub", "DinersClub": 24 | return "DinersClub" 25 | 26 | case "5", "JC", "JCB": 27 | return "JCB" 28 | } 29 | 30 | return creditCardType 31 | } 32 | 33 | // getCountryCode return valid country code from it's name 34 | func getCountryCode(country string) string { 35 | 36 | switch country { 37 | case "USA", "US", "United States", "UnitedStates": 38 | return "US" 39 | 40 | default: 41 | for code, name := range models.ConstCountriesList { 42 | if name == country { 43 | return code 44 | } 45 | } 46 | return "US" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/actors/payment/stripe/decl.go: -------------------------------------------------------------------------------- 1 | package stripe 2 | 3 | // Stripe package constants 4 | const ( 5 | ConstPaymentCode = "stripe" 6 | ConstPaymentName = "Stripe" 7 | 8 | ConstConfigPathGroup = "payment.stripe" 9 | ConstConfigPathEnabled = "payment.stripe.enabled" 10 | ConstConfigPathName = "payment.stripe.name" 11 | ConstConfigPathAPIKey = "payment.stripe.apiKey" 12 | 13 | ConstErrorModule = "payment/stripe" 14 | ) 15 | 16 | // Payment is the struct to hold the payment information for a visitor's order 17 | type Payment struct{} 18 | -------------------------------------------------------------------------------- /app/actors/payment/stripe/init.go: -------------------------------------------------------------------------------- 1 | package stripe 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | 6 | "github.com/ottemo/commerce/app/models/checkout" 7 | ) 8 | 9 | func init() { 10 | if err := checkout.RegisterPaymentMethod(new(Payment)); err != nil { 11 | _ = env.ErrorNew(ConstErrorModule, env.ConstErrorLevelActor, "ea29fa2a-f947-4e7f-aff0-b0965256c751", err.Error()) 12 | } 13 | env.RegisterOnConfigStart(setupConfig) 14 | } 15 | -------------------------------------------------------------------------------- /app/actors/payment/zeropay/decl.go: -------------------------------------------------------------------------------- 1 | // Package zeropay is a "Zero Payment" implementation of payment method interface declared in 2 | // "github.com/ottemo/commerce/app/models/checkout" package 3 | package zeropay 4 | 5 | import ( 6 | "github.com/ottemo/commerce/env" 7 | ) 8 | 9 | // Package global constants 10 | const ( 11 | ConstPaymentZeroPaymentCode = "zeropay" 12 | ConstPaymentName = "Zero Pay" 13 | 14 | ConstConfigPathGroup = "payment.zeropay" 15 | ConstConfigPathEnabled = "payment.zeropay.enabled" 16 | ConstConfigPathName = "payment.zeropay.name" 17 | 18 | ConstErrorModule = "payment/zeropay" 19 | ConstErrorLevel = env.ConstErrorLevelActor 20 | ) 21 | 22 | // ZeroAmountPayment is a implementer of InterfacePaymentMethod for zero amount payments 23 | type ZeroAmountPayment struct{} 24 | -------------------------------------------------------------------------------- /app/actors/payment/zeropay/init.go: -------------------------------------------------------------------------------- 1 | package zeropay 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | 6 | "github.com/ottemo/commerce/app/models/checkout" 7 | ) 8 | 9 | // init makes package self-initialization routine 10 | func init() { 11 | if err := checkout.RegisterPaymentMethod(new(ZeroAmountPayment)); err != nil { 12 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "c2f3aaed-7a1e-4b43-bc31-40cdec749ac5", err.Error()) 13 | } 14 | env.RegisterOnConfigStart(setupConfig) 15 | } 16 | -------------------------------------------------------------------------------- /app/actors/product/decl.go: -------------------------------------------------------------------------------- 1 | // Package product is a implementation of interfaces declared in 2 | // "github.com/ottemo/commerce/app/models/product" package 3 | package product 4 | 5 | import ( 6 | "github.com/ottemo/commerce/app/helpers/attributes" 7 | "github.com/ottemo/commerce/db" 8 | "github.com/ottemo/commerce/env" 9 | ) 10 | 11 | // Package global constants 12 | const ( 13 | ConstCollectionNameProduct = "product" 14 | 15 | ConstErrorModule = "product" 16 | ConstErrorLevel = env.ConstErrorLevelActor 17 | 18 | ConstProductMediaTypeImage = "image" 19 | 20 | ConstSwatchImageDefaultFormat = "jpeg" 21 | ConstSwatchImageDefaultExtention = "jpeg" 22 | ) 23 | 24 | // DefaultProduct is a default implementer of InterfaceProduct 25 | type DefaultProduct struct { 26 | id string 27 | 28 | Enabled bool 29 | 30 | Sku string 31 | Name string 32 | 33 | ShortDescription string 34 | Description string 35 | 36 | DefaultImage string 37 | 38 | Price float64 39 | 40 | Weight float64 41 | 42 | Options map[string]interface{} 43 | 44 | RelatedProductIds []string 45 | 46 | Visible bool 47 | 48 | // appliedOptions tracks options were applied to current instance 49 | appliedOptions map[string]interface{} 50 | 51 | // updatedQty holds qty should be updated during save operation ("" item holds qty value) 52 | updatedQty []map[string]interface{} 53 | 54 | customAttributes *attributes.ModelCustomAttributes 55 | externalAttributes *attributes.ModelExternalAttributes 56 | } 57 | 58 | // DefaultProductCollection is a default implementer of InterfaceProduct 59 | type DefaultProductCollection struct { 60 | listCollection db.InterfaceDBCollection 61 | listExtraAtributes []string 62 | } 63 | -------------------------------------------------------------------------------- /app/actors/product/review/decl.go: -------------------------------------------------------------------------------- 1 | // Package review is a set of API functions to provide an ability to make reviews for a particular product 2 | package review 3 | 4 | import ( 5 | "github.com/ottemo/commerce/env" 6 | ) 7 | 8 | // Package global constants 9 | const ( 10 | ConstReviewCollectionName = "review" 11 | ConstRatingCollectionName = "rating" 12 | 13 | ConstErrorModule = "product/review" 14 | ConstErrorLevel = env.ConstErrorLevelActor 15 | ) 16 | -------------------------------------------------------------------------------- /app/actors/reporting/init.go: -------------------------------------------------------------------------------- 1 | package reporting 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | ) 6 | 7 | func init() { 8 | api.RegisterOnRestServiceStart(setupAPI) 9 | } 10 | -------------------------------------------------------------------------------- /app/actors/rts/config.go: -------------------------------------------------------------------------------- 1 | package rts 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | ) 6 | 7 | func setupConfig() error { 8 | 9 | config := env.GetConfig() 10 | if config == nil { 11 | err := env.ErrorNew(ConstErrorModule, env.ConstErrorLevelStartStop, "a0c79b8f-5782-40bf-bae9-f0108e38d344", "Error configuring rts module") 12 | return env.ErrorDispatch(err) 13 | } 14 | 15 | err := config.RegisterItem(env.StructConfigItem{ 16 | Path: ConstConfigPathCheckoutPath, 17 | Value: "/checkout", 18 | Type: env.ConstConfigTypeText, 19 | Editor: "", 20 | Options: nil, 21 | Label: "Checkout page path", 22 | Description: "", 23 | Image: "", 24 | }, nil) 25 | 26 | if err != nil { 27 | return env.ErrorDispatch(err) 28 | } 29 | 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /app/actors/seo/decl.go: -------------------------------------------------------------------------------- 1 | // Package seo implements a set of API intended to provide SEO optimizations 2 | package seo 3 | 4 | import ( 5 | "github.com/ottemo/commerce/db" 6 | "github.com/ottemo/commerce/env" 7 | ) 8 | 9 | // Package global constants 10 | const ( 11 | ConstCollectionNameURLRewrites = "url_rewrites" 12 | 13 | ConstSitemapFilePath = "sitemap.xml" 14 | ConstSitemapExpireSec = 60 * 60 * 24 15 | 16 | ConstErrorModule = "seo" 17 | ConstErrorLevel = env.ConstErrorLevelActor 18 | ) 19 | 20 | // DefaultSEOItem is a default implementer of InterfaceSEOItem 21 | type DefaultSEOItem struct { 22 | id string 23 | 24 | URL string 25 | Rewrite string 26 | 27 | Type string 28 | Title string 29 | MetaKeywords string 30 | MetaDescription string 31 | } 32 | 33 | // DefaultSEOCollection is a default implementer of InterfaceSEOCollection 34 | type DefaultSEOCollection struct { 35 | listCollection db.InterfaceDBCollection 36 | listExtraAtributes []string 37 | } 38 | -------------------------------------------------------------------------------- /app/actors/seo/helpers.go: -------------------------------------------------------------------------------- 1 | package seo 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | 6 | "github.com/ottemo/commerce/app/models" 7 | "github.com/ottemo/commerce/app/models/seo" 8 | ) 9 | 10 | // GetSEOItemCollectionModel retrieves current InterfaceSEOCollection model implementation 11 | func GetSEOItemCollectionModel() (seo.InterfaceSEOCollection, error) { 12 | model, err := models.GetModel(ConstCollectionNameURLRewrites) 13 | if err != nil { 14 | return nil, env.ErrorDispatch(err) 15 | } 16 | 17 | seoItemCollectionModel, ok := model.(seo.InterfaceSEOCollection) 18 | if !ok { 19 | return nil, env.ErrorNew( 20 | ConstErrorModule, 21 | ConstErrorLevel, 22 | "2198576e-2bf4-4631-a8b3-52b6f661f693", 23 | "model "+model.GetImplementationName()+" is not 'InterfaceSEOCollection' capable") 24 | } 25 | 26 | return seoItemCollectionModel, nil 27 | } 28 | -------------------------------------------------------------------------------- /app/actors/seo/listable.go: -------------------------------------------------------------------------------- 1 | package seo 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/seo" 6 | ) 7 | 8 | // GetCollection returns collection of current instance type 9 | func (it *DefaultSEOItem) GetCollection() models.InterfaceCollection { 10 | model, err := models.GetModel(ConstCollectionNameURLRewrites) 11 | if err != nil { 12 | return nil 13 | } 14 | if result, ok := model.(seo.InterfaceSEOCollection); ok { 15 | return result 16 | } 17 | 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /app/actors/shipping/fedex/init.go: -------------------------------------------------------------------------------- 1 | package fedex 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models/checkout" 5 | "github.com/ottemo/commerce/env" 6 | ) 7 | 8 | // init makes package self-initialization routine 9 | func init() { 10 | if err := checkout.RegisterShippingMethod(new(FedEx)); err != nil { 11 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "619e374e-c247-4db5-81fd-2baf8dd6f9f6", err.Error()) 12 | } 13 | env.RegisterOnConfigStart(setupConfig) 14 | } 15 | -------------------------------------------------------------------------------- /app/actors/shipping/flatrate/decl.go: -------------------------------------------------------------------------------- 1 | // Package flatrate is a Flat Rate implementation of shipping method interface declared in 2 | // "github.com/ottemo/commerce/app/models/checkout" package 3 | package flatrate 4 | 5 | import ( 6 | "github.com/ottemo/commerce/env" 7 | ) 8 | 9 | // Package global constants 10 | const ( 11 | ConstShippingCode = "flat_rate" 12 | ConstShippingName = "Flat Rate" 13 | 14 | ConstConfigPathGroup = "shipping.flat_rate" 15 | 16 | ConstConfigPathEnabled = "shipping.flat_rate.enabled" 17 | ConstConfigPathRates = "shipping.flat_rate.rates" 18 | 19 | ConstErrorModule = "shipping/flatrate" 20 | ConstErrorLevel = env.ConstErrorLevelActor 21 | ) 22 | 23 | // Package global variables 24 | var ( 25 | flatRates []interface{} 26 | ) 27 | 28 | // ShippingMethod is a implementer of InterfaceShippingMethod for a "Flat Rate" shipping method 29 | type ShippingMethod struct{} 30 | -------------------------------------------------------------------------------- /app/actors/shipping/flatrate/init.go: -------------------------------------------------------------------------------- 1 | package flatrate 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app" 5 | "github.com/ottemo/commerce/app/models/checkout" 6 | "github.com/ottemo/commerce/env" 7 | "github.com/ottemo/commerce/utils" 8 | ) 9 | 10 | // init makes package self-initialization routine 11 | func init() { 12 | instance := new(ShippingMethod) 13 | 14 | app.OnAppStart(onAppStart) 15 | if err := checkout.RegisterShippingMethod(instance); err != nil { 16 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "66cfb31e-66ff-43e8-bcd3-ddff50af3249", err.Error()) 17 | } 18 | 19 | env.RegisterOnConfigStart(setupConfig) 20 | } 21 | 22 | // onAppStart makes module initialization on application startup 23 | func onAppStart() error { 24 | 25 | rules, err := utils.DecodeJSONToArray(env.ConfigGetValue(ConstConfigPathRates)) 26 | if err != nil { 27 | rules = make([]interface{}, 0) 28 | } 29 | 30 | flatRates = rules 31 | 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /app/actors/shipping/flatweight/decl.go: -------------------------------------------------------------------------------- 1 | package flatweight 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | 6 | "github.com/ottemo/commerce/app/models/checkout" 7 | ) 8 | 9 | const ( 10 | ConstShippingCode = "flat_weight" 11 | ConstShippingName = "Flat Weight" 12 | 13 | ConstConfigPathGroup = "shipping.flat_weight" 14 | ConstConfigPathEnabled = "shipping.flat_weight.enabled" 15 | ConstConfigPathRates = "shipping.flat_weight.rates" 16 | 17 | ConstErrorModule = "shipping/flatweight" 18 | ConstErrorLevel = env.ConstErrorLevelActor 19 | ) 20 | 21 | // Package global vars 22 | var ( 23 | rates Rates 24 | ) 25 | 26 | // ShippingMethod is a implementer of InterfaceShippingMethod for a "Flat Weight" shipping method 27 | type ShippingMethod struct{} 28 | 29 | type Rates []Rate 30 | 31 | type Rate struct { 32 | Title string 33 | Code string 34 | Price float64 35 | WeightFrom float64 36 | WeightTo float64 37 | AllowedCountries string 38 | BannedCountries string 39 | } 40 | 41 | func (it Rate) validForWeight(weight float64) bool { 42 | return weight >= it.WeightFrom && weight < it.WeightTo 43 | } 44 | 45 | func (it Rate) toCheckoutStruct() checkout.StructShippingRate { 46 | return checkout.StructShippingRate{ 47 | Code: it.Code, 48 | Name: it.Title, 49 | Price: it.Price, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/actors/shipping/flatweight/init.go: -------------------------------------------------------------------------------- 1 | package flatweight 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models/checkout" 5 | "github.com/ottemo/commerce/env" 6 | ) 7 | 8 | func init() { 9 | // rates global is auto-populated via the config declaration 10 | env.RegisterOnConfigStart(setupConfig) 11 | 12 | i := new(ShippingMethod) 13 | if err := checkout.RegisterShippingMethod(i); err != nil { 14 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "30e9fc93-1841-4429-b5a1-c7c6cf7cd3b7", err.Error()) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/actors/shipping/usps/init.go: -------------------------------------------------------------------------------- 1 | package usps 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | 6 | "github.com/ottemo/commerce/app/models/checkout" 7 | ) 8 | 9 | // init makes package self-initialization routine before app start 10 | func init() { 11 | instance := new(USPS) 12 | 13 | if err := checkout.RegisterShippingMethod(instance); err != nil { 14 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "16fcb0df-c6ed-4ae2-bb4b-53468e587685", err.Error()) 15 | } 16 | 17 | env.RegisterOnConfigStart(setupConfig) 18 | } 19 | -------------------------------------------------------------------------------- /app/actors/stock/helpers.go: -------------------------------------------------------------------------------- 1 | package stock 2 | 3 | import ( 4 | "github.com/ottemo/commerce/utils" 5 | ) 6 | 7 | // haveInventoryOptionsDuplicates checks inventory for duplicates 8 | func haveInventoryOptionsDuplicates(inventory interface{}) bool { 9 | var inventoryArray = utils.InterfaceToArray(inventory) 10 | 11 | for idxA, itemA := range inventoryArray { 12 | var itemMapA = utils.InterfaceToMap(itemA) 13 | if _, present := itemMapA["options"]; present { 14 | var optionsA = utils.InterfaceToMap(itemMapA["options"]) 15 | for idxB, itemB := range inventoryArray { 16 | var itemMapB = utils.InterfaceToMap(itemB) 17 | if idxB > idxA { 18 | if _, present := itemMapB["options"]; present { 19 | var optionsB = utils.InterfaceToMap(itemMapB["options"]) 20 | var isEqual = utils.MatchMapAValuesToMapB(optionsA, optionsB) && utils.MatchMapAValuesToMapB(optionsB, optionsA) 21 | 22 | if isEqual { 23 | return true 24 | } 25 | } 26 | } 27 | } 28 | } 29 | } 30 | 31 | return false 32 | } 33 | -------------------------------------------------------------------------------- /app/actors/stock/i_listable.go: -------------------------------------------------------------------------------- 1 | package stock 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/stock" 6 | ) 7 | 8 | // GetCollection returns collection of current instance type 9 | func (it *DefaultStock) GetCollection() models.InterfaceCollection { 10 | model, err := models.GetModel(stock.ConstModelNameStockCollection) 11 | if err != nil { 12 | return nil 13 | } 14 | if result, ok := model.(stock.InterfaceStockCollection); ok { 15 | return result 16 | } 17 | 18 | return nil 19 | } 20 | -------------------------------------------------------------------------------- /app/actors/stock/init.go: -------------------------------------------------------------------------------- 1 | package stock 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | "github.com/ottemo/commerce/db" 6 | "github.com/ottemo/commerce/env" 7 | "github.com/ottemo/commerce/app/models" 8 | "github.com/ottemo/commerce/app/models/stock" 9 | ) 10 | 11 | // init makes package self-initialization routine 12 | func init() { 13 | 14 | instance := new(DefaultStock) 15 | var _ stock.InterfaceStock = instance 16 | if err := models.RegisterModel(stock.ConstModelNameStock, instance); err != nil { 17 | _ = env.ErrorDispatch(err) 18 | } 19 | 20 | stockCollectionInstance := new(DefaultStockCollection) 21 | var _ stock.InterfaceStockCollection = stockCollectionInstance 22 | if err := models.RegisterModel(stock.ConstModelNameStockCollection, stockCollectionInstance); err != nil { 23 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "42552c35-a4ef-40e6-aa38-5d69d4e92578", err.Error()) 24 | } 25 | 26 | stockDelegate = new(StockDelegate) 27 | api.RegisterOnRestServiceStart(setupAPI) 28 | db.RegisterOnDatabaseStart(setupDB) 29 | env.RegisterOnConfigStart(setupConfig) 30 | } 31 | 32 | // setupDB prepares system database for package usage 33 | func setupDB() error { 34 | 35 | if collection, err := db.GetCollection(ConstCollectionNameStock); err == nil { 36 | if err := collection.AddColumn("product_id", db.ConstTypeID, true); err != nil { 37 | return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "d7641743-7d3a-4e1e-9627-fc4b1fad85d1", err.Error()) 38 | } 39 | if err := collection.AddColumn("options", db.ConstTypeJSON, true); err != nil { 40 | return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "7516f60e-83f6-4b72-baa8-ae5c698f1a81", err.Error()) 41 | } 42 | if err := collection.AddColumn("qty", db.ConstTypeInteger, false); err != nil { 43 | return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "a1ce04d7-7a61-4318-a50f-5e3113b6183d", err.Error()) 44 | } 45 | } else { 46 | return env.ErrorDispatch(err) 47 | } 48 | 49 | return nil 50 | } 51 | -------------------------------------------------------------------------------- /app/actors/subscription/collection.i_model.go: -------------------------------------------------------------------------------- 1 | package subscription 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/subscription" 6 | "github.com/ottemo/commerce/db" 7 | "github.com/ottemo/commerce/env" 8 | ) 9 | 10 | // GetModelName returns model name for the Subscription Collection 11 | func (it *DefaultSubscriptionCollection) GetModelName() string { 12 | return subscription.ConstModelNameSubscriptionCollection 13 | } 14 | 15 | // GetImplementationName returns model implementation name for the Subscription Collection 16 | func (it *DefaultSubscriptionCollection) GetImplementationName() string { 17 | return "Default" + subscription.ConstModelNameSubscriptionCollection 18 | } 19 | 20 | // New returns new instance of model implementation object for the Subscription Collection 21 | func (it *DefaultSubscriptionCollection) New() (models.InterfaceModel, error) { 22 | dbCollection, err := db.GetCollection(ConstCollectionNameSubscription) 23 | if err != nil { 24 | return nil, env.ErrorDispatch(err) 25 | } 26 | 27 | return &DefaultSubscriptionCollection{listCollection: dbCollection, listExtraAtributes: make([]string, 0)}, nil 28 | } 29 | -------------------------------------------------------------------------------- /app/actors/subscription/collection.i_subscription_collection.go: -------------------------------------------------------------------------------- 1 | package subscription 2 | 3 | import ( 4 | "github.com/ottemo/commerce/db" 5 | "github.com/ottemo/commerce/env" 6 | 7 | "github.com/ottemo/commerce/app/models/subscription" 8 | ) 9 | 10 | // GetDBCollection returns database collection for the Subscription 11 | func (it *DefaultSubscriptionCollection) GetDBCollection() db.InterfaceDBCollection { 12 | return it.listCollection 13 | } 14 | 15 | // ListSubscriptions returns list of subscription model items in the Subscription Collection 16 | func (it *DefaultSubscriptionCollection) ListSubscriptions() []subscription.InterfaceSubscription { 17 | var result []subscription.InterfaceSubscription 18 | 19 | dbRecords, err := it.listCollection.Load() 20 | if err != nil { 21 | return result 22 | } 23 | 24 | for _, recordData := range dbRecords { 25 | subscriptionModel, err := subscription.GetSubscriptionModel() 26 | if err != nil { 27 | return result 28 | } 29 | if err := subscriptionModel.FromHashMap(recordData); err != nil { 30 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "da49add1-620a-40b3-940d-075c190f7c9a", err.Error()) 31 | } 32 | 33 | result = append(result, subscriptionModel) 34 | } 35 | 36 | return result 37 | } 38 | -------------------------------------------------------------------------------- /app/actors/subscription/decl.go: -------------------------------------------------------------------------------- 1 | // Package subscription implements base subscription functionality 2 | package subscription 3 | 4 | import ( 5 | "time" 6 | 7 | "github.com/ottemo/commerce/app/models/checkout" 8 | "github.com/ottemo/commerce/app/models/subscription" 9 | "github.com/ottemo/commerce/db" 10 | "github.com/ottemo/commerce/env" 11 | ) 12 | 13 | // Package global constants 14 | const ( 15 | ConstErrorModule = "subscription" 16 | ConstErrorLevel = env.ConstErrorLevelActor 17 | 18 | ConstCollectionNameSubscription = "subscription" 19 | 20 | ConstTimeDay = time.Hour * 24 21 | 22 | ConstSchedulerTaskName = "subscriptionProcess" 23 | ) 24 | 25 | var ( 26 | subscriptionProducts = make([]string, 0) // stores id's of products that should be subscriptional 27 | subscriptionEnabled = false 28 | ) 29 | 30 | // DefaultSubscription struct to hold subscription information and represent 31 | // default implementer of InterfaceSubscription 32 | type DefaultSubscription struct { 33 | id string 34 | 35 | VisitorID string 36 | OrderID string 37 | 38 | items []subscription.StructSubscriptionItem 39 | 40 | CustomerEmail string 41 | CustomerName string 42 | 43 | Status string 44 | State string 45 | ActionDate time.Time 46 | Period int 47 | 48 | ShippingAddress map[string]interface{} 49 | BillingAddress map[string]interface{} 50 | 51 | ShippingMethodCode string 52 | 53 | ShippingRate checkout.StructShippingRate 54 | 55 | // should be stored credit card info with payment method in it 56 | PaymentInstrument map[string]interface{} 57 | 58 | LastSubmit time.Time 59 | 60 | CreatedAt time.Time 61 | UpdatedAt time.Time 62 | 63 | Info map[string]interface{} 64 | } 65 | 66 | // DefaultSubscriptionCollection is a default implementer of InterfaceSubscriptionCollection 67 | type DefaultSubscriptionCollection struct { 68 | listCollection db.InterfaceDBCollection 69 | listExtraAtributes []string 70 | } 71 | -------------------------------------------------------------------------------- /app/actors/subscription/subscription.i_listable.go: -------------------------------------------------------------------------------- 1 | package subscription 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/subscription" 6 | ) 7 | 8 | // GetCollection returns collection of current instance type 9 | func (it *DefaultSubscription) GetCollection() models.InterfaceCollection { 10 | model, _ := models.GetModel(subscription.ConstModelNameSubscriptionCollection) 11 | if result, ok := model.(subscription.InterfaceSubscriptionCollection); ok { 12 | return result 13 | } 14 | 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /app/actors/subscription/subscription.i_model.go: -------------------------------------------------------------------------------- 1 | package subscription 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/subscription" 6 | ) 7 | 8 | // GetModelName returns model name for the Subscription 9 | func (it *DefaultSubscription) GetModelName() string { 10 | return subscription.ConstModelNameSubscription 11 | } 12 | 13 | // GetImplementationName returns model implementation name for the Subscription 14 | func (it *DefaultSubscription) GetImplementationName() string { 15 | return "Default" + subscription.ConstModelNameSubscription 16 | } 17 | 18 | // New returns new instance of model implementation object for the Subscription 19 | func (it *DefaultSubscription) New() (models.InterfaceModel, error) { 20 | return &DefaultSubscription{Info: make(map[string]interface{})}, nil 21 | } 22 | -------------------------------------------------------------------------------- /app/actors/subscription/subscription.i_storable.go: -------------------------------------------------------------------------------- 1 | package subscription 2 | 3 | import ( 4 | "github.com/ottemo/commerce/db" 5 | "github.com/ottemo/commerce/env" 6 | "time" 7 | ) 8 | 9 | // GetID returns current ID of the Subscription 10 | func (it *DefaultSubscription) GetID() string { 11 | return it.id 12 | } 13 | 14 | // SetID sets current ID of the current Subscription 15 | func (it *DefaultSubscription) SetID(NewID string) error { 16 | it.id = NewID 17 | return nil 18 | } 19 | 20 | // Load will retrieve the Subscription information from database 21 | func (it *DefaultSubscription) Load(ID string) error { 22 | 23 | collection, err := db.GetCollection(ConstCollectionNameSubscription) 24 | if err != nil { 25 | return env.ErrorDispatch(err) 26 | } 27 | 28 | values, err := collection.LoadByID(ID) 29 | if err != nil { 30 | return env.ErrorDispatch(err) 31 | } 32 | 33 | err = it.FromHashMap(values) 34 | return env.ErrorDispatch(err) 35 | } 36 | 37 | // Delete removes current Subscription from the database 38 | func (it *DefaultSubscription) Delete() error { 39 | 40 | collection, err := db.GetCollection(ConstCollectionNameSubscription) 41 | if err != nil { 42 | return env.ErrorDispatch(err) 43 | } 44 | 45 | err = collection.DeleteByID(it.GetID()) 46 | return env.ErrorDispatch(err) 47 | } 48 | 49 | // Save stores current Subscription to the database 50 | func (it *DefaultSubscription) Save() error { 51 | 52 | collection, err := db.GetCollection(ConstCollectionNameSubscription) 53 | if err != nil { 54 | return env.ErrorDispatch(err) 55 | } 56 | 57 | // checking required fields for creating new subscription 58 | // if err := it.Validate(); err != nil { 59 | // return env.ErrorDispatch(err) 60 | // } 61 | 62 | // update time depend values 63 | currentTime := time.Now() 64 | if it.CreatedAt.IsZero() { 65 | it.CreatedAt = currentTime 66 | } 67 | it.UpdatedAt = currentTime 68 | 69 | storingValues := it.ToHashMap() 70 | 71 | // saving operation 72 | newID, err := collection.Save(storingValues) 73 | if err != nil { 74 | return env.ErrorDispatch(err) 75 | } 76 | 77 | return it.SetID(newID) 78 | } 79 | -------------------------------------------------------------------------------- /app/actors/swatch/decl.go: -------------------------------------------------------------------------------- 1 | // Package swatch is a default implementation of product swatches 2 | package swatch 3 | 4 | import ( 5 | "github.com/ottemo/commerce/media" 6 | ) 7 | 8 | // Package global constants 9 | const ( 10 | ConstErrorModule = "swatch" 11 | 12 | ConstStorageModel = "swatch" 13 | ConstStorageObjectID = "media" 14 | ConstStorageMediaType = "image" 15 | 16 | ConstImageDefaultFormat = "png" 17 | ConstImageDefaultExtention = "png" 18 | ) 19 | 20 | var ( 21 | mediaStorage media.InterfaceMediaStorage 22 | ) 23 | -------------------------------------------------------------------------------- /app/actors/swatch/init.go: -------------------------------------------------------------------------------- 1 | package swatch 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | "github.com/ottemo/commerce/app" 6 | "github.com/ottemo/commerce/env" 7 | "github.com/ottemo/commerce/media" 8 | ) 9 | 10 | // init makes package self-initialization routine 11 | func init() { 12 | app.OnAppStart(onAppStart) 13 | api.RegisterOnRestServiceStart(setupAPI) 14 | } 15 | 16 | func onAppStart() error { 17 | 18 | var err error 19 | mediaStorage, err = media.GetMediaStorage() 20 | if err != nil { 21 | return env.ErrorDispatch(err) 22 | } 23 | 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /app/actors/tax/decl.go: -------------------------------------------------------------------------------- 1 | // Package tax is a implementation of tax interface declared in 2 | // "github.com/ottemo/commerce/app/models/checkout" package 3 | package tax 4 | 5 | import ( 6 | "github.com/ottemo/commerce/env" 7 | ) 8 | 9 | // Package global constants 10 | const ( 11 | ConstErrorModule = "tax" 12 | ConstErrorLevel = env.ConstErrorLevelActor 13 | 14 | ConstPriorityValue = 2.50 15 | 16 | ConstProductTaxableAttribute = "taxable" 17 | ) 18 | 19 | var priority float64 20 | 21 | // DefaultTax is a default implementer of InterfaceTax 22 | type DefaultTax struct{} 23 | -------------------------------------------------------------------------------- /app/actors/visitor/address/address.i_listable.go: -------------------------------------------------------------------------------- 1 | package address 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/visitor" 6 | ) 7 | 8 | // GetCollection returns collection of current instance type 9 | func (it *DefaultVisitorAddress) GetCollection() models.InterfaceCollection { 10 | model, _ := models.GetModel(visitor.ConstModelNameVisitorAddressCollection) 11 | if result, ok := model.(visitor.InterfaceVisitorAddressCollection); ok { 12 | return result 13 | } 14 | 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /app/actors/visitor/address/address.i_model.go: -------------------------------------------------------------------------------- 1 | package address 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/visitor" 6 | ) 7 | 8 | // GetModelName returns the Visitor Address Model 9 | func (it *DefaultVisitorAddress) GetModelName() string { 10 | return visitor.ConstModelNameVisitorAddress 11 | } 12 | 13 | // GetImplementationName returns the Implementation name 14 | func (it *DefaultVisitorAddress) GetImplementationName() string { 15 | return "Default" + visitor.ConstModelNameVisitorAddress 16 | } 17 | 18 | // New creates a new Visitor Address interface 19 | func (it *DefaultVisitorAddress) New() (models.InterfaceModel, error) { 20 | return &DefaultVisitorAddress{}, nil 21 | } 22 | -------------------------------------------------------------------------------- /app/actors/visitor/address/address.i_visitor_address.go: -------------------------------------------------------------------------------- 1 | package address 2 | 3 | // GetVisitorID returns the Visitor ID for the Visitor Address 4 | func (it *DefaultVisitorAddress) GetVisitorID() string { return it.visitorID } 5 | 6 | // GetFirstName returns the First Name of the Visitor Address 7 | func (it *DefaultVisitorAddress) GetFirstName() string { return it.FirstName } 8 | 9 | // GetLastName returns the Last Name of the Visitor Address 10 | func (it *DefaultVisitorAddress) GetLastName() string { return it.LastName } 11 | 12 | // GetCompany will return the Company attribute of the Visitor Address 13 | func (it *DefaultVisitorAddress) GetCompany() string { return it.Company } 14 | 15 | // GetCountry will return the Country attribute of the Visitor Address 16 | func (it *DefaultVisitorAddress) GetCountry() string { return it.Country } 17 | 18 | // GetState will return the State attribute of the Visitor Address 19 | func (it *DefaultVisitorAddress) GetState() string { return it.State } 20 | 21 | // GetCity will return the City attribute of the Visitor Address 22 | func (it *DefaultVisitorAddress) GetCity() string { return it.City } 23 | 24 | // GetAddress will return the full Address of the current Visitor Address 25 | func (it *DefaultVisitorAddress) GetAddress() string { return it.AddressLine1 + " " + it.AddressLine2 } 26 | 27 | // GetAddressLine1 will return the Line 1 attribute of the Visitor Address 28 | func (it *DefaultVisitorAddress) GetAddressLine1() string { return it.AddressLine1 } 29 | 30 | // GetAddressLine2 will return the Line 2 attribute of the Visitor Address 31 | func (it *DefaultVisitorAddress) GetAddressLine2() string { return it.AddressLine2 } 32 | 33 | // GetPhone will return the phone attribute of the Visitor Address 34 | func (it *DefaultVisitorAddress) GetPhone() string { return it.Phone } 35 | 36 | // GetZipCode will return the zip code attribute of the Visitor Address 37 | func (it *DefaultVisitorAddress) GetZipCode() string { return it.ZipCode } 38 | -------------------------------------------------------------------------------- /app/actors/visitor/address/collection.i_model.go: -------------------------------------------------------------------------------- 1 | package address 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/visitor" 6 | "github.com/ottemo/commerce/db" 7 | "github.com/ottemo/commerce/env" 8 | ) 9 | 10 | // GetModelName returns the Visitor Address model 11 | func (it *DefaultVisitorAddressCollection) GetModelName() string { 12 | return visitor.ConstModelNameVisitorAddress 13 | } 14 | 15 | // GetImplementationName returns the Visitor Address implementation name 16 | func (it *DefaultVisitorAddressCollection) GetImplementationName() string { 17 | return "Default" + visitor.ConstModelNameVisitorAddress 18 | } 19 | 20 | // New creates a new Visitor Address Collection 21 | func (it *DefaultVisitorAddressCollection) New() (models.InterfaceModel, error) { 22 | dbCollection, err := db.GetCollection(ConstCollectionNameVisitorAddress) 23 | if err != nil { 24 | return nil, env.ErrorDispatch(err) 25 | } 26 | 27 | return &DefaultVisitorAddressCollection{listCollection: dbCollection, listExtraAtributes: make([]string, 0)}, nil 28 | } 29 | -------------------------------------------------------------------------------- /app/actors/visitor/address/collection.i_visitor_address_collection.go: -------------------------------------------------------------------------------- 1 | package address 2 | 3 | import ( 4 | "github.com/ottemo/commerce/db" 5 | "github.com/ottemo/commerce/env" 6 | 7 | "github.com/ottemo/commerce/app/models/visitor" 8 | ) 9 | 10 | // GetDBCollection returns the database collection of the Visitor Address 11 | func (it *DefaultVisitorAddressCollection) GetDBCollection() db.InterfaceDBCollection { 12 | return it.listCollection 13 | } 14 | 15 | // ListVisitorsAddresses returns list of visitor model items for the Visitor Address 16 | func (it *DefaultVisitorAddressCollection) ListVisitorsAddresses() []visitor.InterfaceVisitorAddress { 17 | var result []visitor.InterfaceVisitorAddress 18 | 19 | dbRecords, err := it.listCollection.Load() 20 | if err != nil { 21 | return result 22 | } 23 | 24 | for _, recordData := range dbRecords { 25 | visitorAddressModel, err := visitor.GetVisitorAddressModel() 26 | if err != nil { 27 | return result 28 | } 29 | if err := visitorAddressModel.FromHashMap(recordData); err != nil { 30 | _ = env.ErrorDispatch(err) 31 | } 32 | 33 | result = append(result, visitorAddressModel) 34 | } 35 | 36 | return result 37 | } 38 | -------------------------------------------------------------------------------- /app/actors/visitor/address/decl.go: -------------------------------------------------------------------------------- 1 | // Package address is a default implementation of models/visitor package visitor address related interfaces 2 | package address 3 | 4 | import ( 5 | "github.com/ottemo/commerce/db" 6 | "github.com/ottemo/commerce/env" 7 | ) 8 | 9 | // Package global constants 10 | const ( 11 | ConstCollectionNameVisitorAddress = "visitor_address" 12 | 13 | ConstErrorModule = "visitor/address" 14 | ConstErrorLevel = env.ConstErrorLevelActor 15 | ) 16 | 17 | // DefaultVisitorAddress is a default implementer of InterfaceVisitorAddress 18 | type DefaultVisitorAddress struct { 19 | id string 20 | visitorID string 21 | 22 | FirstName string 23 | LastName string 24 | 25 | Company string 26 | 27 | Country string 28 | State string 29 | City string 30 | 31 | AddressLine1 string 32 | AddressLine2 string 33 | 34 | Phone string 35 | ZipCode string 36 | } 37 | 38 | // DefaultVisitorAddressCollection is a default implementer of InterfaceVisitorAddressCollection 39 | type DefaultVisitorAddressCollection struct { 40 | listCollection db.InterfaceDBCollection 41 | listExtraAtributes []string 42 | } 43 | -------------------------------------------------------------------------------- /app/actors/visitor/collection.i_model.go: -------------------------------------------------------------------------------- 1 | package visitor 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/visitor" 6 | "github.com/ottemo/commerce/db" 7 | "github.com/ottemo/commerce/env" 8 | ) 9 | 10 | // GetModelName returns model name for the Visitor Collection 11 | func (it *DefaultVisitorCollection) GetModelName() string { 12 | return visitor.ConstModelNameVisitorCollection 13 | } 14 | 15 | // GetImplementationName returns model implementation name for the Visitor Collection 16 | func (it *DefaultVisitorCollection) GetImplementationName() string { 17 | return "Default" + visitor.ConstModelNameVisitorCollection 18 | } 19 | 20 | // New returns new instance of model implementation object for the Visitor Collection 21 | func (it *DefaultVisitorCollection) New() (models.InterfaceModel, error) { 22 | dbCollection, err := db.GetCollection(ConstCollectionNameVisitor) 23 | if err != nil { 24 | return nil, env.ErrorDispatch(err) 25 | } 26 | 27 | return &DefaultVisitorCollection{listCollection: dbCollection, listExtraAtributes: make([]string, 0)}, nil 28 | } 29 | -------------------------------------------------------------------------------- /app/actors/visitor/collection.i_visitor_collection.go: -------------------------------------------------------------------------------- 1 | package visitor 2 | 3 | import ( 4 | "github.com/ottemo/commerce/db" 5 | "github.com/ottemo/commerce/env" 6 | 7 | "github.com/ottemo/commerce/app/models/visitor" 8 | ) 9 | 10 | // GetDBCollection returns database collection for the Visitor 11 | func (it *DefaultVisitorCollection) GetDBCollection() db.InterfaceDBCollection { 12 | return it.listCollection 13 | } 14 | 15 | // ListVisitors returns list of visitor model items in the Visitor Collection 16 | func (it *DefaultVisitorCollection) ListVisitors() []visitor.InterfaceVisitor { 17 | var result []visitor.InterfaceVisitor 18 | 19 | dbRecords, err := it.listCollection.Load() 20 | if err != nil { 21 | return result 22 | } 23 | 24 | for _, recordData := range dbRecords { 25 | visitorModel, err := visitor.GetVisitorModel() 26 | if err != nil { 27 | return result 28 | } 29 | if err := visitorModel.FromHashMap(recordData); err != nil { 30 | _ = env.ErrorNew(ConstErrorModule, ConstErrorLevel, "7c4664a6-52eb-419f-adec-df7b7fd146a1", err.Error()) 31 | } 32 | 33 | result = append(result, visitorModel) 34 | } 35 | 36 | return result 37 | } 38 | -------------------------------------------------------------------------------- /app/actors/visitor/config.go: -------------------------------------------------------------------------------- 1 | package visitor 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | ) 6 | 7 | // setupConfig setups package configuration values for a system 8 | func setupConfig() error { 9 | config := env.GetConfig() 10 | if config == nil { 11 | err := env.ErrorNew(ConstErrorModule, env.ConstErrorLevelStartStop, "b196712b-99c3-4510-bd58-935c5eedfc9f", "Unable to obtain configuration") 12 | return env.ErrorDispatch(err) 13 | } 14 | 15 | err := config.RegisterItem(env.StructConfigItem{ 16 | Path: ConstConfigPathLostPasswordEmailSubject, 17 | Value: "", 18 | Type: env.ConstConfigTypeVarchar, 19 | Editor: "line_text", 20 | Options: "", 21 | Label: "Lost Password Email Subject", 22 | Description: "", 23 | Image: "", 24 | }, nil) 25 | if err != nil { 26 | return env.ErrorDispatch(err) 27 | } 28 | 29 | err = config.RegisterItem(env.StructConfigItem{ 30 | Path: ConstConfigPathLostPasswordEmailTemplate, 31 | Value: "", 32 | Type: env.ConstConfigTypeText, 33 | Editor: "multiline_text", 34 | Options: "", 35 | Label: "Lost Password Email Template", 36 | Description: "", 37 | Image: "", 38 | }, nil) 39 | 40 | if err != nil { 41 | return env.ErrorDispatch(err) 42 | } 43 | 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /app/actors/visitor/decl.go: -------------------------------------------------------------------------------- 1 | // Package visitor is a default implementation of models/visitor package visitor related interfaces 2 | package visitor 3 | 4 | import ( 5 | "time" 6 | 7 | "github.com/ottemo/commerce/app/helpers/attributes" 8 | "github.com/ottemo/commerce/app/models/visitor" 9 | "github.com/ottemo/commerce/db" 10 | "github.com/ottemo/commerce/env" 11 | ) 12 | 13 | // Package global constants 14 | const ( 15 | ConstCollectionNameVisitor = "visitor" 16 | 17 | ConstEmailVerifyExpire = 60 * 60 * 24 18 | 19 | ConstEmailPasswordResetExpire = 30 * 60 20 | 21 | ConstErrorModule = "visitor" 22 | ConstErrorLevel = env.ConstErrorLevelActor 23 | 24 | ConstConfigPathLostPasswordEmailSubject = "general.mail.lost_password_email_subject" 25 | ConstConfigPathLostPasswordEmailTemplate = "general.mail.lost_password_email_template" 26 | ) 27 | 28 | // DefaultVisitor is a default implementer of InterfaceVisitor 29 | type DefaultVisitor struct { 30 | id string 31 | 32 | Email string 33 | FacebookID string 34 | GoogleID string 35 | 36 | FirstName string 37 | LastName string 38 | 39 | BillingAddress visitor.InterfaceVisitorAddress 40 | ShippingAddress visitor.InterfaceVisitorAddress 41 | 42 | Token visitor.InterfaceVisitorCard 43 | 44 | Password string 45 | VerificationKey string 46 | 47 | Admin bool 48 | 49 | CreatedAt time.Time 50 | 51 | *attributes.ModelCustomAttributes 52 | } 53 | 54 | // DefaultVisitorCollection is a default implementer of InterfaceVisitorCollection 55 | type DefaultVisitorCollection struct { 56 | listCollection db.InterfaceDBCollection 57 | listExtraAtributes []string 58 | } 59 | -------------------------------------------------------------------------------- /app/actors/visitor/token/collection.i_model.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/visitor" 6 | "github.com/ottemo/commerce/db" 7 | "github.com/ottemo/commerce/env" 8 | ) 9 | 10 | // GetModelName returns the Visitor Address model 11 | func (it *DefaultVisitorCardCollection) GetModelName() string { 12 | return visitor.ConstModelNameVisitorCard 13 | } 14 | 15 | // GetImplementationName returns the Visitor Address implementation name 16 | func (it *DefaultVisitorCardCollection) GetImplementationName() string { 17 | return "Default" + visitor.ConstModelNameVisitorCard 18 | } 19 | 20 | // New creates a new Visitor Address Collection 21 | func (it *DefaultVisitorCardCollection) New() (models.InterfaceModel, error) { 22 | dbCollection, err := db.GetCollection(ConstCollectionNameVisitorToken) 23 | if err != nil { 24 | return nil, env.ErrorDispatch(err) 25 | } 26 | 27 | return &DefaultVisitorCardCollection{listCollection: dbCollection, listExtraAtributes: make([]string, 0)}, nil 28 | } 29 | -------------------------------------------------------------------------------- /app/actors/visitor/token/collection.i_visitor_card_collection.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models/visitor" 5 | "github.com/ottemo/commerce/db" 6 | "github.com/ottemo/commerce/env" 7 | ) 8 | 9 | // GetDBCollection returns the database collection of the Visitor Cards 10 | func (it *DefaultVisitorCardCollection) GetDBCollection() db.InterfaceDBCollection { 11 | return it.listCollection 12 | } 13 | 14 | // ListVisitorsCards returns list of visitor model items for the Visitor Cards 15 | func (it *DefaultVisitorCardCollection) ListVisitorsCards() []visitor.InterfaceVisitorCard { 16 | var result []visitor.InterfaceVisitorCard 17 | 18 | dbRecords, err := it.listCollection.Load() 19 | if err != nil { 20 | return result 21 | } 22 | 23 | for _, recordData := range dbRecords { 24 | visitorCardModel, err := visitor.GetVisitorCardModel() 25 | if err != nil { 26 | return result 27 | } 28 | if err := visitorCardModel.FromHashMap(recordData); err != nil { 29 | _ = env.ErrorDispatch(err) 30 | } 31 | 32 | result = append(result, visitorCardModel) 33 | } 34 | 35 | return result 36 | } 37 | -------------------------------------------------------------------------------- /app/actors/visitor/token/decl.go: -------------------------------------------------------------------------------- 1 | // Package token allows to create and use tokens 2 | package token 3 | 4 | import ( 5 | "github.com/ottemo/commerce/db" 6 | "github.com/ottemo/commerce/env" 7 | "time" 8 | ) 9 | 10 | // Package global constants 11 | const ( 12 | ConstCollectionNameVisitorToken = "visitor_token" 13 | 14 | ConstErrorModule = "visitor/token" 15 | ConstErrorLevel = env.ConstErrorLevelActor 16 | ) 17 | 18 | // DefaultVisitorCard is a default implementer of InterfaceVisitorCard 19 | type DefaultVisitorCard struct { 20 | id string 21 | visitorID string 22 | 23 | Holder string 24 | Payment string 25 | 26 | Type string 27 | Number string 28 | 29 | ExpirationDate string 30 | ExpirationMonth int 31 | ExpirationYear int 32 | 33 | CreatedAt time.Time 34 | TokenUpdated time.Time 35 | 36 | tokenID string 37 | customerID string 38 | } 39 | 40 | // DefaultVisitorCardCollection is a default implementer of InterfaceVisitorCardCollection 41 | type DefaultVisitorCardCollection struct { 42 | listCollection db.InterfaceDBCollection 43 | listExtraAtributes []string 44 | } 45 | -------------------------------------------------------------------------------- /app/actors/visitor/token/token.i_model.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/visitor" 6 | ) 7 | 8 | // GetModelName returns the Visitor Address Model 9 | func (it *DefaultVisitorCard) GetModelName() string { 10 | return visitor.ConstModelNameVisitorCard 11 | } 12 | 13 | // GetImplementationName returns the Implementation name 14 | func (it *DefaultVisitorCard) GetImplementationName() string { 15 | return "Default" + visitor.ConstModelNameVisitorCard 16 | } 17 | 18 | // New creates a new Visitor Address interface 19 | func (it *DefaultVisitorCard) New() (models.InterfaceModel, error) { 20 | return &DefaultVisitorCard{}, nil 21 | } 22 | -------------------------------------------------------------------------------- /app/actors/visitor/token/token.i_storable.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "github.com/ottemo/commerce/db" 5 | "github.com/ottemo/commerce/env" 6 | "github.com/ottemo/commerce/utils" 7 | "time" 8 | ) 9 | 10 | // GetID returns the Default Visitor Token as a string 11 | func (it *DefaultVisitorCard) GetID() string { 12 | return it.id 13 | } 14 | 15 | // SetID takes a string as input and sets the ID on the Visitor Token 16 | func (it *DefaultVisitorCard) SetID(NewID string) error { 17 | it.id = NewID 18 | return nil 19 | } 20 | 21 | // Load will take Visitor Token ID and retrieve it from the database 22 | func (it *DefaultVisitorCard) Load(loadID string) error { 23 | 24 | collection, err := db.GetCollection(ConstCollectionNameVisitorToken) 25 | if err != nil { 26 | return env.ErrorDispatch(err) 27 | } 28 | 29 | dbRecord, err := collection.LoadByID(loadID) 30 | if err != nil { 31 | return env.ErrorDispatch(err) 32 | } 33 | 34 | return it.FromHashMap(dbRecord) 35 | } 36 | 37 | // Delete will remove the Visitor Token from the database 38 | func (it *DefaultVisitorCard) Delete() error { 39 | 40 | collection, err := db.GetCollection(ConstCollectionNameVisitorToken) 41 | if err != nil { 42 | return env.ErrorDispatch(err) 43 | } 44 | 45 | return collection.DeleteByID(it.GetID()) 46 | } 47 | 48 | // Save will persist the Visitor Token to the database 49 | func (it *DefaultVisitorCard) Save() error { 50 | 51 | collection, err := db.GetCollection(ConstCollectionNameVisitorToken) 52 | if err != nil { 53 | return env.ErrorDispatch(err) 54 | } 55 | 56 | if it.tokenID == "" || it.Payment == "" { 57 | return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "dbd526dd-e0ae-4f43-a8cd-2da8ff3bba8a", "payment and token should be specified") 58 | } 59 | 60 | if utils.IsZeroTime(it.CreatedAt) && it.GetID() == "" { 61 | it.CreatedAt = time.Now() 62 | } 63 | 64 | storableValues := it.ToHashMap() 65 | 66 | newID, err := collection.Save(storableValues) 67 | if err != nil { 68 | return env.ErrorDispatch(err) 69 | } 70 | 71 | return it.SetID(newID) 72 | } 73 | -------------------------------------------------------------------------------- /app/actors/visitor/token/token.i_visitor_card.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "github.com/ottemo/commerce/utils" 5 | "time" 6 | ) 7 | 8 | // GetVisitorID returns the Visitor ID for the Visitor Card 9 | func (it *DefaultVisitorCard) GetVisitorID() string { 10 | return it.visitorID 11 | } 12 | 13 | // GetHolderName returns the Holder of the Credit Card 14 | func (it *DefaultVisitorCard) GetHolderName() string { 15 | return it.Holder 16 | } 17 | 18 | // GetPaymentMethodCode returns the Payment method code of the Visitor Card 19 | func (it *DefaultVisitorCard) GetPaymentMethodCode() string { 20 | return it.Payment 21 | } 22 | 23 | // GetType will return the Type of the Visitor Card 24 | func (it *DefaultVisitorCard) GetType() string { 25 | return it.Type 26 | } 27 | 28 | // GetNumber will return the Number attribute of the Visitor Card 29 | func (it *DefaultVisitorCard) GetNumber() string { 30 | return it.Number 31 | } 32 | 33 | // GetExpirationDate will return the Expiration date of the Visitor Card 34 | func (it *DefaultVisitorCard) GetExpirationDate() string { 35 | 36 | if it.ExpirationDate == "" { 37 | it.ExpirationDate = utils.InterfaceToString(it.ExpirationMonth) + "/" + utils.InterfaceToString(it.ExpirationYear) 38 | } 39 | 40 | return it.ExpirationDate 41 | } 42 | 43 | // GetToken will return the Token of the Visitor Card 44 | func (it *DefaultVisitorCard) GetToken() string { 45 | return it.tokenID 46 | } 47 | 48 | // GetCustomerID will return the customer_id field of the Visitor Card 49 | func (it *DefaultVisitorCard) GetCustomerID() string { 50 | return it.customerID 51 | } 52 | 53 | // IsExpired will return Expired status of the Visitor Card 54 | func (it *DefaultVisitorCard) IsExpired() bool { 55 | current := time.Now() 56 | return it.ExpirationYear < utils.InterfaceToInt(current.Year()) || it.ExpirationMonth < utils.InterfaceToInt(current.Month()) 57 | } 58 | -------------------------------------------------------------------------------- /app/actors/visitor/visitor.i_listable.go: -------------------------------------------------------------------------------- 1 | package visitor 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/app/models/visitor" 6 | ) 7 | 8 | // GetCollection returns collection of current instance type 9 | func (it *DefaultVisitor) GetCollection() models.InterfaceCollection { 10 | model, _ := models.GetModel(visitor.ConstModelNameVisitorCollection) 11 | if result, ok := model.(visitor.InterfaceVisitorCollection); ok { 12 | return result 13 | } 14 | 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /app/actors/visitor/visitor.i_model.go: -------------------------------------------------------------------------------- 1 | package visitor 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/helpers/attributes" 5 | "github.com/ottemo/commerce/app/models" 6 | "github.com/ottemo/commerce/app/models/visitor" 7 | "github.com/ottemo/commerce/env" 8 | ) 9 | 10 | // GetModelName returns model name for the Visitor 11 | func (it *DefaultVisitor) GetModelName() string { 12 | return visitor.ConstModelNameVisitor 13 | } 14 | 15 | // GetImplementationName returns model implementation name for the Visitor 16 | func (it *DefaultVisitor) GetImplementationName() string { 17 | return "Default" + visitor.ConstModelNameVisitor 18 | } 19 | 20 | // New returns new instance of model implementation object for the Visitor 21 | func (it *DefaultVisitor) New() (models.InterfaceModel, error) { 22 | 23 | customAttributes, err := attributes.CustomAttributes(visitor.ConstModelNameVisitor, ConstCollectionNameVisitor) 24 | if err != nil { 25 | return nil, env.ErrorDispatch(err) 26 | } 27 | 28 | return &DefaultVisitor{ModelCustomAttributes: customAttributes}, nil 29 | } 30 | -------------------------------------------------------------------------------- /app/actors/xdomain/api.go: -------------------------------------------------------------------------------- 1 | package xdomain 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/ottemo/commerce/api" 7 | ) 8 | 9 | // endpoint configuration for xdomain package 10 | func setupAPI() error { 11 | 12 | service := api.GetRestService() 13 | service.GET("proxy.html", xdomainHandler) 14 | 15 | return nil 16 | } 17 | 18 | // xdomainHandler will enable the usage of xdomain instead of CORS for legacy browsers 19 | func xdomainHandler(context api.InterfaceApplicationContext) (interface{}, error) { 20 | 21 | responseWriter := context.GetResponseWriter() 22 | 23 | newline := []byte("\n") 24 | 25 | if err := context.SetResponseContentType("text/html"); err != nil { 26 | fmt.Println("06a968c4-cd17-43cf-a93e-b7317b53b883", err) 27 | } 28 | 29 | if _, err := responseWriter.Write([]byte("")); err != nil { 30 | fmt.Println("c02a9d84-3c24-4998-b060-2bbb92bea6da", err) 31 | } 32 | if _, err := responseWriter.Write(newline); err != nil { 33 | fmt.Println("3968707c-c8e4-4e91-a4ed-f5869619e1bc", err) 34 | } 35 | if _, err := responseWriter.Write([]byte("")); err != nil { 36 | fmt.Println("5b815f17-42a6-42a5-8591-82674f1a86bd", err) 37 | } 38 | if _, err := responseWriter.Write(newline); err != nil { 39 | fmt.Println("a6c11509-a68b-4920-bee2-3a8ab93ea30c", err) 40 | } 41 | 42 | return "", nil 43 | } 44 | -------------------------------------------------------------------------------- /app/actors/xdomain/decl.go: -------------------------------------------------------------------------------- 1 | package xdomain 2 | 3 | import "github.com/ottemo/commerce/env" 4 | 5 | // xdomain package constants 6 | const ( 7 | ConstErrorModule = "xdomain" 8 | ConstErrorLevel = env.ConstErrorLevelActor 9 | ) 10 | 11 | // xdomain package level globals 12 | var ( 13 | xdomainMasterURL = "http://*.staging.ottemo.io/" // default to all stores in staging 14 | ) 15 | -------------------------------------------------------------------------------- /app/actors/xdomain/init.go: -------------------------------------------------------------------------------- 1 | package xdomain 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | "github.com/ottemo/commerce/env" 6 | ) 7 | 8 | // init performs self-initialization routine before app start 9 | func init() { 10 | 11 | env.RegisterOnConfigIniStart(setupIniConfig) 12 | api.RegisterOnRestServiceStart(setupAPI) 13 | } 14 | 15 | // setupIniConfig reads the setting from the ottemo.ini file 16 | func setupIniConfig() error { 17 | 18 | if iniConfig := env.GetIniConfig(); iniConfig != nil { 19 | if iniValue := iniConfig.GetValue("xdomain.master", xdomainMasterURL); iniValue != "" { 20 | xdomainMasterURL = iniValue 21 | } 22 | } 23 | 24 | return nil 25 | } 26 | -------------------------------------------------------------------------------- /app/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Ottemo. All rights reserved. 2 | 3 | /* 4 | 5 | Package app represents Ottemo application object. 6 | 7 | That package contains routines which allows other components to register callbacks on application start/end, API 8 | functions for administrator login, system configuration values, etc. So, this package contains the code addressed to 9 | application instance. Ottemo packages should address this package to interact with running application instance but not 10 | to "github.com/ottemo/foundaton" package". 11 | 12 | */ 13 | package app 14 | -------------------------------------------------------------------------------- /app/helpers/attributes/other_test.go: -------------------------------------------------------------------------------- 1 | package attributes 2 | 3 | import "testing" 4 | 5 | // TestExternalAttributes tests concurrent execution for an ExternalAttributes 6 | func TestExternalAttributes(t *testing.T) { 7 | const concurrent = 9999 8 | finished := make(chan int) 9 | 10 | routines := concurrent 11 | for i := 0; i < routines; i++ { 12 | go func(i int) { 13 | ExampleExternalAttributes() 14 | 15 | finished <- i 16 | }(i) 17 | } 18 | 19 | for routines > 0 { 20 | <-finished 21 | routines-- 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/helpers/objectref/decl.go: -------------------------------------------------------------------------------- 1 | // Package objectref intended to unify and simplify a way of model instance changes tracking (currently not implemented) 2 | package objectref 3 | 4 | import ( 5 | "github.com/ottemo/commerce/env" 6 | ) 7 | 8 | // Package global constants 9 | const ( 10 | ConstErrorModule = "objectref" 11 | ConstErrorLevel = env.ConstErrorLevelHelper 12 | ) 13 | 14 | // DBObjectRef is a object state tracking helper and implementer of InterfaceObject and InterfaceStorable 15 | type DBObjectRef struct { 16 | id string 17 | 18 | loaded bool 19 | modified bool 20 | 21 | origData map[string]interface{} 22 | currData map[string]interface{} 23 | } 24 | 25 | // MarkAsLoaded marks object instance as loaded from DB 26 | func (it *DBObjectRef) MarkAsLoaded() { 27 | it.loaded = true 28 | } 29 | 30 | // MarkAsModified marks object instance as modified 31 | func (it *DBObjectRef) MarkAsModified() { 32 | it.modified = true 33 | } 34 | 35 | // IsModified returns value of modification flag 36 | func (it *DBObjectRef) IsModified() bool { 37 | return it.modified 38 | } 39 | 40 | // IsLoaded returns value of load from DB flag 41 | func (it *DBObjectRef) IsLoaded() bool { 42 | return it.loaded 43 | } 44 | -------------------------------------------------------------------------------- /app/helpers/objectref/i_storable.go: -------------------------------------------------------------------------------- 1 | package objectref 2 | 3 | import "github.com/ottemo/commerce/env" 4 | 5 | // GetID returns current object id 6 | func (it *DBObjectRef) GetID() string { 7 | return it.id 8 | } 9 | 10 | // SetID sets new id to current object 11 | func (it *DBObjectRef) SetID(id string) { 12 | it.id = id 13 | } 14 | 15 | // Save stores current product to DB 16 | func (it *DBObjectRef) Save() error { 17 | return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "d41d76c0-fa63-4418-b90e-bde64864ecaa", "not implemented") 18 | } 19 | 20 | // Load loads information from DB 21 | func (it *DBObjectRef) Load(id string) error { 22 | return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "d65b0e21-c51d-4460-a32a-e4f09a84f421", "not implemented") 23 | } 24 | 25 | // Delete removes current object instance from DB 26 | func (it *DBObjectRef) Delete() error { 27 | return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "61c0b53d-b2a4-499a-be8b-6f2101f1ab97", "not implemented") 28 | } 29 | -------------------------------------------------------------------------------- /app/init.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | "github.com/ottemo/commerce/env" 6 | ) 7 | 8 | // init makes package self-initialization routine 9 | func init() { 10 | env.RegisterOnConfigStart(setupConfig) 11 | api.RegisterOnRestServiceStart(setupAPI) 12 | } 13 | -------------------------------------------------------------------------------- /app/models/blog/post/helpers.go: -------------------------------------------------------------------------------- 1 | package post 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | 6 | "github.com/ottemo/commerce/app/models" 7 | ) 8 | 9 | // GetBlogPostModel retrieves current InterfaceBlogPost model implementation 10 | func GetBlogPostModel() (InterfaceBlogPost, error) { 11 | model, err := models.GetModel(ConstModelNameBlogPost) 12 | if err != nil { 13 | return nil, env.ErrorDispatch(err) 14 | } 15 | 16 | blogPostModel, ok := model.(InterfaceBlogPost) 17 | if !ok { 18 | return nil, env.ErrorNew(ConstErrorModule, ConstErrorLevel, "d2fd8dc4-094e-4a32-8e89-d8be2f3bf1ea", "model "+model.GetImplementationName()+" is not 'InterfaceBlogPost' capable") 19 | } 20 | 21 | return blogPostModel, nil 22 | } 23 | -------------------------------------------------------------------------------- /app/models/blog/post/interfaces.go: -------------------------------------------------------------------------------- 1 | // Package post represents abstraction of business layer blog post object 2 | package post 3 | 4 | import ( 5 | "time" 6 | 7 | "github.com/ottemo/commerce/env" 8 | 9 | "github.com/ottemo/commerce/app/models" 10 | ) 11 | 12 | // Package global constants 13 | const ( 14 | ConstModelNameBlogPost = "BlogPost" 15 | 16 | ConstErrorModule = "blog" 17 | ConstErrorLevel = env.ConstErrorLevelModel 18 | ) 19 | 20 | // InterfaceBlogPost represents interface to access business layer implementation of blog post object 21 | type InterfaceBlogPost interface { 22 | GetIdentifier() string 23 | SetIdentifier(string) error 24 | IsPublished() bool 25 | SetPublished(bool) error 26 | 27 | GetTitle() string 28 | SetTitle(string) error 29 | GetExcerpt() string 30 | SetExcerpt(string) error 31 | GetContent() string 32 | SetContent(string) error 33 | 34 | GetTags() []interface{} 35 | SetTags([]interface{}) error 36 | GetFeaturedImage() string 37 | SetFeaturedImage(string) error 38 | 39 | GetCreatedAt() time.Time 40 | SetCreatedAt(time.Time) error 41 | GetUpdatedAt() time.Time 42 | SetUpdatedAt(time.Time) error 43 | 44 | models.InterfaceModel 45 | models.InterfaceObject 46 | models.InterfaceStorable 47 | } 48 | -------------------------------------------------------------------------------- /app/models/cart/interfaces.go: -------------------------------------------------------------------------------- 1 | // Package cart represents abstraction of business layer cart object 2 | package cart 3 | 4 | import ( 5 | "time" 6 | 7 | "github.com/ottemo/commerce/app/models" 8 | "github.com/ottemo/commerce/app/models/product" 9 | "github.com/ottemo/commerce/app/models/visitor" 10 | "github.com/ottemo/commerce/env" 11 | ) 12 | 13 | // Package global constants 14 | const ( 15 | ConstCartModelName = "Cart" 16 | ConstSessionKeyCurrentCart = "cart_id" 17 | 18 | ConstErrorModule = "cart" 19 | ConstErrorLevel = env.ConstErrorLevelModel 20 | ) 21 | 22 | // InterfaceCartItem represents interface to access business layer implementation of cart item object 23 | type InterfaceCartItem interface { 24 | GetID() string 25 | SetID(newID string) error 26 | 27 | GetIdx() int 28 | SetIdx(newIdx int) error 29 | 30 | GetProductID() string 31 | GetProduct() product.InterfaceProduct 32 | 33 | GetQty() int 34 | SetQty(qty int) error 35 | 36 | GetOptions() map[string]interface{} 37 | SetOption(optionName string, optionValue interface{}) error 38 | 39 | ValidateProduct() error 40 | 41 | GetCart() InterfaceCart 42 | } 43 | 44 | // InterfaceCart represents interface to access business layer implementation of cart object 45 | type InterfaceCart interface { 46 | AddItem(productID string, qty int, options map[string]interface{}) (InterfaceCartItem, error) 47 | RemoveItem(itemIdx int) error 48 | SetQty(itemIdx int, qty int) error 49 | 50 | GetItems() []InterfaceCartItem 51 | GetSubtotal() float64 52 | 53 | GetVisitorID() string 54 | SetVisitorID(string) error 55 | 56 | GetVisitor() visitor.InterfaceVisitor 57 | MakeCartForVisitor(visitorID string) error 58 | 59 | GetSessionID() string 60 | SetSessionID(sessionID string) error 61 | 62 | Activate() error 63 | Deactivate() error 64 | IsActive() bool 65 | 66 | GetLastUpdateTime() time.Time 67 | 68 | SetCartInfo(infoAttribute string, infoValue interface{}) error 69 | GetCartInfo() map[string]interface{} 70 | 71 | SetCustomInfo(info map[string]interface{}) 72 | GetCustomInfo() map[string]interface{} 73 | 74 | ValidateCart() error 75 | 76 | models.InterfaceModel 77 | models.InterfaceStorable 78 | } 79 | -------------------------------------------------------------------------------- /app/models/category/interfaces.go: -------------------------------------------------------------------------------- 1 | // Package category represents abstraction of business layer category object 2 | package category 3 | 4 | import ( 5 | "github.com/ottemo/commerce/app/models" 6 | "github.com/ottemo/commerce/app/models/product" 7 | "github.com/ottemo/commerce/env" 8 | ) 9 | 10 | // Package global constants 11 | const ( 12 | ConstModelNameCategory = "Category" 13 | ConstModelNameCategoryCollection = "CategoryCollection" 14 | 15 | ConstErrorModule = "category" 16 | ConstErrorLevel = env.ConstErrorLevelModel 17 | ) 18 | 19 | // InterfaceCategory represents interface to access business layer implementation of category object 20 | type InterfaceCategory interface { 21 | GetEnabled() bool 22 | 23 | GetName() string 24 | 25 | GetParent() InterfaceCategory 26 | 27 | GetDescription() string 28 | 29 | GetImage() string 30 | 31 | GetProductIds() []string 32 | GetProductsCollection() product.InterfaceProductCollection 33 | GetProducts() []product.InterfaceProduct 34 | 35 | AddProduct(productID string) error 36 | RemoveProduct(productID string) error 37 | 38 | models.InterfaceModel 39 | models.InterfaceObject 40 | models.InterfaceStorable 41 | models.InterfaceMedia 42 | models.InterfaceListable 43 | } 44 | 45 | // InterfaceCategoryCollection represents interface to access business layer implementation of category collection 46 | type InterfaceCategoryCollection interface { 47 | ListCategories() []InterfaceCategory 48 | 49 | models.InterfaceCollection 50 | } 51 | -------------------------------------------------------------------------------- /app/models/discount/saleprice/decl.go: -------------------------------------------------------------------------------- 1 | package saleprice 2 | 3 | // Public constants 4 | const ( 5 | ConstModelNameSalePrice = "SalePrice" 6 | ConstSalePriceDbCollectionName = "sale_prices" 7 | ) 8 | -------------------------------------------------------------------------------- /app/models/discount/saleprice/helpers.go: -------------------------------------------------------------------------------- 1 | package saleprice 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/env" 6 | ) 7 | 8 | // GetSalePriceModel retrieves current InterfaceSalePrice model implementation 9 | func GetSalePriceModel() (InterfaceSalePrice, error) { 10 | model, err := models.GetModel(ConstModelNameSalePrice) 11 | if err != nil { 12 | return nil, env.ErrorDispatch(err) 13 | } 14 | 15 | salePriceModel, ok := model.(InterfaceSalePrice) 16 | if !ok { 17 | return nil, env.ErrorNew(ConstErrorModule, ConstErrorLevel, "1ef4f461-e355-4989-9d79-75b060d8153c", "model "+model.GetImplementationName()+" is not 'InterfaceSalePrice' capable") 18 | } 19 | 20 | return salePriceModel, nil 21 | } 22 | 23 | // GetSalePriceCollectionModel retrieves current InterfaceSalePriceCollection model implementation 24 | func GetSalePriceCollectionModel() (InterfaceSalePriceCollection, error) { 25 | model, err := models.GetModel(ConstSalePriceDbCollectionName) 26 | if err != nil { 27 | return nil, env.ErrorDispatch(err) 28 | } 29 | 30 | salePriceCollectionModel, ok := model.(InterfaceSalePriceCollection) 31 | if !ok { 32 | return nil, env.ErrorNew(ConstErrorModule, ConstErrorLevel, "a6fb5051-626d-4ab7-8e44-5b8b2a1180b5", "model "+model.GetImplementationName()+" is not 'InterfaceSalePriceCollection' capable") 33 | } 34 | 35 | return salePriceCollectionModel, nil 36 | } 37 | -------------------------------------------------------------------------------- /app/models/discount/saleprice/interfaces.go: -------------------------------------------------------------------------------- 1 | package saleprice 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/ottemo/commerce/app/models" 7 | "github.com/ottemo/commerce/env" 8 | ) 9 | 10 | // Package global constants 11 | const ( 12 | ConstErrorModule = "saleprice" 13 | ConstErrorLevel = env.ConstErrorLevelModel 14 | ) 15 | 16 | // InterfaceSalePrice represents interface to access business layer implementation of sale price object 17 | type InterfaceSalePrice interface { 18 | GetAmount() float64 19 | SetAmount(float64) error 20 | 21 | GetEndDatetime() time.Time 22 | SetEndDatetime(time.Time) error 23 | 24 | GetProductID() string 25 | SetProductID(string) error 26 | 27 | GetStartDatetime() time.Time 28 | SetStartDatetime(time.Time) error 29 | 30 | models.InterfaceObject 31 | models.InterfaceStorable 32 | } 33 | 34 | // InterfaceSalePriceCollection represents interface to access business layer implementation of sale price collection 35 | type InterfaceSalePriceCollection interface { 36 | ListSalePrices() []InterfaceSalePrice 37 | 38 | models.InterfaceCollection 39 | } 40 | -------------------------------------------------------------------------------- /app/models/manager.go: -------------------------------------------------------------------------------- 1 | package models 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | ) 6 | 7 | // RegisterModel registers new model to system 8 | func RegisterModel(ModelName string, Model InterfaceModel) error { 9 | if _, present := declaredModels[ModelName]; present { 10 | return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "0300eb6b-08b8-497e-afd0-eda0ee358596", "The model with name '"+ModelName+"' has already been registered") 11 | } 12 | declaredModels[ModelName] = Model 13 | 14 | return nil 15 | } 16 | 17 | // UnRegisterModel removes registered model from system 18 | func UnRegisterModel(ModelName string) error { 19 | if _, present := declaredModels[ModelName]; present { 20 | delete(declaredModels, ModelName) 21 | } else { 22 | return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "3d651c0a-e4a4-443f-a6e8-de3a95d89b5c", "Unable to find model to delete with name '"+ModelName+"'") 23 | } 24 | return nil 25 | } 26 | 27 | // GetModel returns registered in system model 28 | func GetModel(ModelName string) (InterfaceModel, error) { 29 | if model, present := declaredModels[ModelName]; present { 30 | return model.New() 31 | } 32 | return nil, env.ErrorNew(ConstErrorModule, ConstErrorLevel, "5d49fd0d-1fed-47dc-8e72-2346f1e778c3", "Unable to find model with name '"+ModelName+"'") 33 | } 34 | 35 | // GetDeclaredModels returns all currently registered in system models 36 | func GetDeclaredModels() map[string]InterfaceModel { 37 | return declaredModels 38 | } 39 | -------------------------------------------------------------------------------- /app/models/product/manager.go: -------------------------------------------------------------------------------- 1 | package product 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | "github.com/ottemo/commerce/app/models/stock" 6 | ) 7 | 8 | // Package global variables 9 | var ( 10 | registeredStock stock.InterfaceStock 11 | ) 12 | 13 | // UnRegisterStock removes stock management from system 14 | func UnRegisterStock() error { 15 | registeredStock = nil 16 | return nil 17 | } 18 | 19 | // RegisterStock registers given stock manager in system 20 | func RegisterStock(stock stock.InterfaceStock) error { 21 | if registeredStock != nil { 22 | return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "80dc18f2-da63-418e-8430-5a832a4c3bd2", "Already registered") 23 | } 24 | registeredStock = stock 25 | 26 | return nil 27 | } 28 | 29 | // GetRegisteredStock returns currently used stack manager or nil 30 | func GetRegisteredStock() stock.InterfaceStock { 31 | return registeredStock 32 | } 33 | -------------------------------------------------------------------------------- /app/models/seo/helpers.go: -------------------------------------------------------------------------------- 1 | package seo 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/env" 6 | ) 7 | 8 | // GetSEO shortcut to registered engine method 9 | func GetSEO(seoType string, objectID string, urlPattern string) []InterfaceSEOItem { 10 | if seoEngine := GetRegisteredSEOEngine(); seoEngine != nil { 11 | return seoEngine.GetSEO(seoType, objectID, urlPattern) 12 | } 13 | return []InterfaceSEOItem{} 14 | } 15 | 16 | // GetSEOItemModel retrieves current InterfaceSEOItem model implementation 17 | func GetSEOItemModel() (InterfaceSEOItem, error) { 18 | model, err := models.GetModel(ConstModelNameSEOItem) 19 | if err != nil { 20 | return nil, env.ErrorDispatch(err) 21 | } 22 | 23 | SEOItemModel, ok := model.(InterfaceSEOItem) 24 | if !ok { 25 | return nil, env.ErrorNew(ConstErrorModule, ConstErrorLevel, "fa4bce5b-c500-4faf-81ba-9d28cfff72fb", "model "+model.GetImplementationName()+" is not 'InterfaceSEOItem' capable") 26 | } 27 | 28 | return SEOItemModel, nil 29 | } 30 | 31 | // GetSEOItemModelAndSetID retrieves current InterfaceSEOItem model implementation and sets its ID to some value 32 | func GetSEOItemModelAndSetID(SEOItemID string) (InterfaceSEOItem, error) { 33 | 34 | SEOItemModel, err := GetSEOItemModel() 35 | if err != nil { 36 | return nil, env.ErrorDispatch(err) 37 | } 38 | 39 | err = SEOItemModel.SetID(SEOItemID) 40 | if err != nil { 41 | return SEOItemModel, env.ErrorDispatch(err) 42 | } 43 | 44 | return SEOItemModel, nil 45 | } 46 | 47 | // LoadSEOItemByID loads SEOItem data into current InterfaceSEOItem model implementation 48 | func LoadSEOItemByID(SEOItemID string) (InterfaceSEOItem, error) { 49 | 50 | SEOItemModel, err := GetSEOItemModel() 51 | if err != nil { 52 | return nil, env.ErrorDispatch(err) 53 | } 54 | 55 | err = SEOItemModel.Load(SEOItemID) 56 | if err != nil { 57 | return nil, env.ErrorDispatch(err) 58 | } 59 | 60 | return SEOItemModel, nil 61 | } 62 | -------------------------------------------------------------------------------- /app/models/seo/interfaces.go: -------------------------------------------------------------------------------- 1 | package seo 2 | 3 | import ( 4 | "github.com/ottemo/commerce/app/models" 5 | "github.com/ottemo/commerce/env" 6 | ) 7 | 8 | // Package global constants 9 | const ( 10 | ConstModelNameSEOItem = "SEOItem" 11 | ConstModelNameSEOItemCollection = "SEOCollection" 12 | 13 | ConstErrorModule = "seo" 14 | ConstErrorLevel = env.ConstErrorLevelModel 15 | ) 16 | 17 | // InterfaceSEOEngine represents interface to access business layer implementation of SEO engine 18 | type InterfaceSEOEngine interface { 19 | GetSEO(seoType string, objectID string, urlPattern string) []InterfaceSEOItem 20 | } 21 | 22 | // InterfaceSEOItem represents interface to access business layer implementation of SEO item object 23 | type InterfaceSEOItem interface { 24 | GetURL() string 25 | SetURL(newURL string) error 26 | 27 | GetRewrite() string 28 | 29 | GetTitle() string 30 | GetType() string 31 | GetMetaKeywords() string 32 | GetMetaDescription() string 33 | 34 | models.InterfaceModel 35 | models.InterfaceObject 36 | models.InterfaceStorable 37 | models.InterfaceListable 38 | } 39 | 40 | // InterfaceSEOCollection represents interface to access business layer implementation of SEO items collection 41 | type InterfaceSEOCollection interface { 42 | ListSEOItems() []InterfaceSEOItem 43 | 44 | models.InterfaceCollection 45 | } 46 | -------------------------------------------------------------------------------- /app/models/stock/helpers.go: -------------------------------------------------------------------------------- 1 | package stock 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | 6 | "github.com/ottemo/commerce/app/models" 7 | ) 8 | 9 | // GetBlogPostModel retrieves current InterfaceBlogPost model implementation 10 | func GetStockModel() (InterfaceStock, error) { 11 | model, err := models.GetModel(ConstModelNameStock) 12 | if err != nil { 13 | return nil, env.ErrorDispatch(err) 14 | } 15 | 16 | stockModel, ok := model.(InterfaceStock) 17 | if !ok { 18 | return nil, env.ErrorNew(ConstErrorModule, ConstErrorLevel, "d2fd8dc4-094e-4a32-8e89-d8be2f3bf1ea", "model "+model.GetImplementationName()+" is not 'InterfaceStock' capable") 19 | } 20 | 21 | return stockModel, nil 22 | } 23 | 24 | // GetProductCollectionModel retrieves current InterfaceProductCollection model implementation 25 | func GetStockCollectionModel() (InterfaceStockCollection, error) { 26 | model, err := models.GetModel(ConstModelNameStockCollection) 27 | if err != nil { 28 | return nil, env.ErrorDispatch(err) 29 | } 30 | 31 | stockModel, ok := model.(InterfaceStockCollection) 32 | if !ok { 33 | return nil, env.ErrorNew(ConstErrorModule, ConstErrorLevel, "343218b7-587f-47e5-83a8-a372615116d9", "model "+model.GetImplementationName()+" is not 'InterfaceStockCollection' capable") 34 | } 35 | 36 | return stockModel, nil 37 | } 38 | -------------------------------------------------------------------------------- /app/models/stock/interfaces.go: -------------------------------------------------------------------------------- 1 | // Package product represents abstraction of business layer product object 2 | package stock 3 | 4 | import ( 5 | "github.com/ottemo/commerce/app/models" 6 | "github.com/ottemo/commerce/env" 7 | ) 8 | 9 | // Package global constants 10 | const ( 11 | ConstModelNameStock = "Stock" 12 | ConstModelNameStockCollection = "StockCollection" 13 | 14 | ConstErrorModule = "stock" 15 | ConstErrorLevel = env.ConstErrorLevelModel 16 | 17 | //ConstOptionProductIDs = "_ids" 18 | ) 19 | 20 | // InterfaceStock represents interface to access business layer implementation of stock management 21 | type InterfaceStock interface { 22 | 23 | GetProductID() string 24 | GetOptions() string 25 | GetQty() int 26 | 27 | SetProductID(product_id string) error 28 | SetOptions(options string) error 29 | SetQty(qty int) error 30 | 31 | SetProductQty(productID string, options map[string]interface{}, qty int) error 32 | GetProductQty(productID string, options map[string]interface{}) int 33 | GetProductOptions(productID string) []map[string]interface{} 34 | 35 | RemoveProductQty(productID string, options map[string]interface{}) error 36 | UpdateProductQty(productID string, options map[string]interface{}, deltaQty int) error 37 | 38 | models.InterfaceModel 39 | models.InterfaceObject 40 | models.InterfaceStorable 41 | models.InterfaceListable 42 | } 43 | 44 | // InterfaceStockCollection represents interface to access business layer implementation of stock collection 45 | type InterfaceStockCollection interface { 46 | ListStocks() []InterfaceStock 47 | 48 | models.InterfaceCollection 49 | } 50 | -------------------------------------------------------------------------------- /basebuild/build_mongo.go: -------------------------------------------------------------------------------- 1 | // +build mongo 2 | 3 | package basebuild 4 | 5 | import ( 6 | // MongoDB based database service 7 | _ "github.com/ottemo/commerce/db/mongo" 8 | ) 9 | -------------------------------------------------------------------------------- /basebuild/build_mysql.go: -------------------------------------------------------------------------------- 1 | // +build mysql 2 | 3 | package basebuild 4 | 5 | import ( 6 | // MySQL based database service 7 | _ "github.com/go-sql-driver/mysql" 8 | _ "github.com/ottemo/commerce/db/mysql" 9 | ) 10 | -------------------------------------------------------------------------------- /basebuild/build_postgres.go: -------------------------------------------------------------------------------- 1 | // +build !sqlite,!mysql,!mongo 2 | 3 | package basebuild 4 | 5 | import ( 6 | _ "github.com/ottemo/commerce/db/postgres" 7 | ) 8 | -------------------------------------------------------------------------------- /basebuild/build_sqlite.go: -------------------------------------------------------------------------------- 1 | // +build sqlite 2 | 3 | package basebuild 4 | 5 | import ( 6 | // SQLite based database service 7 | _ "github.com/ottemo/commerce/db/sqlite" 8 | ) 9 | -------------------------------------------------------------------------------- /basebuild/doc.go: -------------------------------------------------------------------------------- 1 | // Package basebuild represents default Ottemo e-commerce product build. 2 | // 3 | // This package declares an application components to use by void usage import of them. So, these void import packages 4 | // are self-init packages of replaceable modules/extension/plugins. 5 | // 6 | // In order to use this package just use void import: 7 | // import _ "github.com/ottemo/commerce/basebuild" 8 | package basebuild 9 | -------------------------------------------------------------------------------- /bin/bootstrap.sh: -------------------------------------------------------------------------------- 1 | # TODO: update this old bootstrap script for setting up commerce the first time in /opt dir 2 | apt-get -y install bzr 3 | apt-get -y install git 4 | apt-get -y install golang-go 5 | 6 | 7 | rm -rf /opt/ottemo 8 | mkdir -pv /opt/ottemo/go/src/github.com/ottemo 9 | mkdir -pv /opt/ottemo/go/bin 10 | mkdir -pv /opt/ottemo/go/pkg 11 | mkdir -pv /opt/ottemo/media 12 | 13 | export GOPATH=/opt/ottemo/go 14 | 15 | git clone https://github.com/ottemo/commerce /opt/ottemo/go/src/github.com/ottemo/commerce 16 | 17 | cd $GOPATH/bin 18 | echo "media.fsmedia.folder=/opt/ottemo/media" >> ottemo.ini 19 | echo "mongodb.db=ottemo-demo" >> ottemo.ini 20 | echo "mongodb.uri=mongodb://DB_USER:DB_PASSWROD@MONGO_DB_URI:27017/ottemo" >> ottemo.ini 21 | 22 | cd $GOPATH/src/github.com/ottemo/commerce && go get -t 23 | cd $GOPATH/src/github.com/ottemo/commerce && go get gopkg.in/mgo.v2 24 | cd $GOPATH/src/github.com/ottemo/commerce && go get gopkg.in/mgo.v2/bson 25 | cd $GOPATH/src/github.com/ottemo/commerce && go build -tags mongo 26 | -------------------------------------------------------------------------------- /bin/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # location of commerce 4 | HOME=/home/ottemo 5 | SRCDIR=/home/ottemo/code/go/src/github.com/ottemo/commerce 6 | 7 | if [ "$BRANCH" == 'develop' ]; then 8 | #GIT_COMMIT=$( echo "$COMMIT" | head -c 5 ) 9 | # grab the latest code 10 | 11 | currentBranch=`ssh ottemo@$REMOTE_HOST "cd $SRCDIR && git symbolic-ref --quiet --short HEAD 2> /dev/null || git rev-parse --short HEAD 2> /dev/null || echo '(unknown)'"` 12 | echo "" 13 | echo "COMMERCE BRANCH IS ${currentBranch}" 14 | 15 | if [ "$currentBranch" == 'develop' ]; then 16 | echo "GRAB THE LATEST CODE" 17 | ssh ottemo@$REMOTE_HOST "cd $SRCDIR && git stash && git checkout develop && git fetch --prune && git pull" 18 | fi 19 | 20 | echo "" 21 | echo "BUILD LOCALLY" 22 | ssh ottemo@$REMOTE_HOST "cd $SRCDIR && go get -t ./... && bash bin/make.sh -tags mongo" 23 | 24 | echo "BACKUP THE CURRENT BINARY AND PUT THE NEWLY BUILT BINARY INTO SERVICE" 25 | ssh ottemo@$REMOTE_HOST "sudo service ottemo stop && mv $HOME/commerce/commerce $HOME/commerce/commerce.bak" 26 | ssh ottemo@$REMOTE_HOST "cp $SRCDIR/commerce ~/commerce/commerce && sudo service ottemo start" 27 | fi 28 | -------------------------------------------------------------------------------- /bin/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo "generate commerce config and start it" 4 | echo "you have to define at least MONGOHOST and MONGODB" 5 | echo "MONGOUSER, MONGOPASS, SECURE_COOKIE, SSL_CERT, XDOMAIN, REDIS are optional parametes" 6 | 7 | if ! [ -n "$MONGOHOST" ] ; then 8 | echo "you have to define MONGOHOST environment parameter" 9 | exit 2 10 | fi 11 | if ! [ -n "$MONGODB" ] ; then 12 | echo "you have to define MONGODB environment parameter" 13 | exit 2 14 | fi 15 | 16 | uri="mongodb://" 17 | if [ -n "$MONGOUSER" ] ; then 18 | if [ -n "$MONGOPASS" ] ; then 19 | uri="$uri$MONGOUSER:$MONGOPASS@" 20 | else 21 | uri="$uri$MONGOUSER@" 22 | fi 23 | fi 24 | uri="$uri$MONGOHOST/$MONGODB" 25 | 26 | if [ -n "$MEDIAFOLDER" ] ; then 27 | mkdir -p $MEDIAFOLDER 28 | mkdir -p /home/ottemo/ 29 | ln -s $MEDIAFOLDER /home/ottemo/media 30 | echo "media.fsmedia.folder=$MEDIAFOLDER" > ottemo.ini 31 | else 32 | echo "media.fsmedia.folder=/home/ottemo/media" > ottemo.ini 33 | fi 34 | 35 | echo "mongodb.db=$MONGODB" >> ottemo.ini 36 | echo "mongodb.uri=$uri" >> ottemo.ini 37 | 38 | if [ -n "$SECURE_COOKIE" ] ; then 39 | echo "secure_cookie=$SECURE_COOKIE" >> ottemo.ini 40 | else 41 | echo "secure_cookie=false" >> ottemo.ini 42 | fi 43 | # ssl cert can be placed to nfs share or mounted as secret into container on kubernetes 44 | if [ -n "$SSL_CERT" ] ; then 45 | echo "ssl.cert=$SSL_CERT" >> ottemo.ini 46 | fi 47 | if [ -n "$XDOMAIN" ] ; then 48 | echo "xdomain=$XDOMAIN" >> ottemo.ini 49 | fi 50 | if [ -n "$REDISHOST" ] ; then 51 | echo "redis.servers=$REDISHOST" >> ottemo.ini 52 | fi 53 | 54 | echo "use follow ottemo.ini config:" 55 | cat ottemo.ini 56 | 57 | ./commerce 58 | -------------------------------------------------------------------------------- /bin/mongo-dump-csv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Use this script to dump all mongodb collections to CSV format for import 4 | # into mysql or sqlite tables. Tables will be dumped into a local folder with 5 | # the same name as DBNAME and dropped into files named after their collection. 6 | 7 | OIFS=$IFS; 8 | IFS=","; 9 | 10 | # fill in your details here 11 | dbname=kg-dev 12 | user=USER 13 | pass=PASSWD 14 | host=candidate.42.mongolayer.com:10243 15 | 16 | # first get all collections in the database 17 | collections=`mongo "$host/$dbname" --username $user --password $pass --eval "rs.slaveOk();db.getCollectionNames();" --quiet`; 18 | collectionArray=($collections); 19 | 20 | # for each collection 21 | for ((i=0; i<${#collectionArray[@]}; ++i)); 22 | do 23 | echo 'exporting collection' ${collectionArray[$i]} 24 | # get comma separated list of keys. do this by peeking into the first document in the collection and get his set of keys 25 | keys=`mongo "$host/$dbname" --username $user --password $pass --eval "rs.slaveOk();var keys = []; for(var key in db.${collectionArray[$i]}.find().sort({_id: -1}).limit(1)[0]) { keys.push(key); }; keys;" --quiet`; 26 | # now use mongoexport with the set of keys to export the collection to csv 27 | mongoexport --host $host --username $user --password $pass --quiet --db $dbname --collection ${collectionArray[$i]} --type=csv --fields "$keys" --out $dbname.${collectionArray[$i]}.csv; 28 | done 29 | 30 | IFS=$OIFS; 31 | -------------------------------------------------------------------------------- /bin/prod_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # vars for script 4 | PWD=$PWD 5 | SRCDIR=/home/ottemo/code/go/src/github.com/ottemo/commerce 6 | 7 | cd $SRCDIR 8 | # Build commerce 9 | echo "BUILDING COMMERCE" 10 | ./bin/make.sh -tags mongo 11 | 12 | # stop the commerce service 13 | echo "STOP COMMERCE SERVICE" 14 | while sudo service ottemo stop >/dev/null 2>&1; do 15 | echo "warning: commerce is still running" 16 | done 17 | echo "info: commerce has terminated" 18 | 19 | echo "DEPLOYING COMMERCE" 20 | # Backup binaries and restart commerce 21 | cp ~/commerce/commerce ~/commerce/commerce.bak 22 | cp commerce ~/commerce/ 23 | sudo service ottemo start 24 | 25 | # restore PWD 26 | cd $PWD 27 | 28 | echo "DEPLOY DONE" 29 | -------------------------------------------------------------------------------- /bin/update-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # location of commerce 4 | SRCDIR=/home/ottemo/code/go/src/github.com/ottemo/commerce 5 | 6 | if [ "$BRANCH" == 'develop' ]; then 7 | GIT_COMMIT=`echo $COMMIT | head -c 5` 8 | # grab the latest code 9 | ssh ottemo@$REMOTE_HOST "cd $SRCDIR && git checkout develop && git fetch --prune && git pull" 10 | # update packate dependencies 11 | ssh ottemo@$REMOTE_HOST "cd $SRCDIR && go get -t ./..." 12 | # restart the documenation service 13 | ssh ottemo@$REMOTE_HOST "sudo service godoc restart" 14 | fi 15 | -------------------------------------------------------------------------------- /db/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Ottemo. All rights reserved. 2 | 3 | /* 4 | Package db represents database services abstraction layer. It provides a set of interfaces and helpers to interact with 5 | database storage services. 6 | 7 | So, when package decides to store something in database it should work with this package instead of trying to interact 8 | with concrete database engine (as Ottemo supposing ability to work with different databases). 9 | 10 | Providing Ottemo with a new database engine supposes implementation of "InterfaceDBEngine" with following registration 11 | for db package. 12 | 13 | For database type specification you should refer this package for possible types. 14 | 15 | Example: 16 | -------- 17 | collection, err := db.GetCollection( myCollectionName ) 18 | if err != nil { 19 | return env.ErrorDispatch(err) 20 | } 21 | 22 | collection.AddColumn("customer_id", db.ConstTypeID, false) 23 | collection.AddColumn("customer_email", db.TypeWPrecision(db.ConstTypeVarchar, 100), true) 24 | collection.AddColumn("bonus_code", db.ConstTypeInteger, false) 25 | collection.AddColumn("bonus_amount", db.ConstTypeInteger, false) 26 | 27 | */ 28 | package db 29 | -------------------------------------------------------------------------------- /db/manager.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | ) 6 | 7 | // Package global variables 8 | var ( 9 | currentDBEngine InterfaceDBEngine // currently registered database service in system 10 | callbacksOnDatabaseStart = []func() error{} // set of callback function on database service start 11 | ) 12 | 13 | // RegisterOnDatabaseStart registers new callback on database service start 14 | func RegisterOnDatabaseStart(callback func() error) { 15 | callbacksOnDatabaseStart = append(callbacksOnDatabaseStart, callback) 16 | } 17 | 18 | // OnDatabaseStart fires database service start event (callback handling) 19 | func OnDatabaseStart() error { 20 | for _, callback := range callbacksOnDatabaseStart { 21 | if err := callback(); err != nil { 22 | return env.ErrorDispatch(err) 23 | } 24 | } 25 | return nil 26 | } 27 | 28 | // RegisterDBEngine registers database service in the system 29 | // - will cause error if there are couple candidates for that role 30 | func RegisterDBEngine(newEngine InterfaceDBEngine) error { 31 | if currentDBEngine == nil { 32 | currentDBEngine = newEngine 33 | } else { 34 | return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "c8588998-a99b-406a-91cb-cb5766e34f7e", "Sorry, '"+currentDBEngine.GetName()+"' already registered") 35 | } 36 | return nil 37 | } 38 | 39 | // GetDBEngine returns currently used database service implementation 40 | func GetDBEngine() InterfaceDBEngine { 41 | return currentDBEngine 42 | } 43 | -------------------------------------------------------------------------------- /db/mongo/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Ottemo. All rights reserved. 2 | 3 | /* 4 | Package mongo is a default mongoDB implementation for Ottemo. It provides "InterfaceDBEngine" implementation declared in 5 | "github.com/ottemo/commerce/db" package. 6 | 7 | Package stands on a top of "gopkg.in/mgo.v2" package. 8 | 9 | MongoDB is the only database that harnesses the innovations of NoSQL (flexibility, scalability, performance) and builds 10 | on the commerce of relational databases (expressive query language, secondary indexes, strong consistency). 11 | */ 12 | package mongo 13 | -------------------------------------------------------------------------------- /db/mongo/init.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "github.com/ottemo/commerce/db" 5 | "github.com/ottemo/commerce/env" 6 | ) 7 | 8 | // init makes package self-initialization routine 9 | func init() { 10 | instance := new(DBEngine) 11 | 12 | var dbConnector = db.NewDBConnector(instance) 13 | env.RegisterOnConfigIniStart(dbConnector.ConnectAsync) 14 | 15 | _ = db.RegisterDBEngine(instance) 16 | } 17 | 18 | // Output is a implementation of mgo.log_Logger interface 19 | func (it *DBEngine) Output(calldepth int, s string) error { 20 | env.Log("mongo.log", "DEBUG", s) 21 | return nil 22 | } 23 | -------------------------------------------------------------------------------- /db/mongo/utils.go: -------------------------------------------------------------------------------- 1 | package mongo 2 | 3 | import ( 4 | "gopkg.in/mgo.v2/bson" 5 | "sort" 6 | "time" 7 | 8 | "github.com/ottemo/commerce/utils" 9 | ) 10 | 11 | // ConvertMapToDoc is a recursive function that converts map[string]interface{} to bson.D 12 | // so map keys order is static and alphabetically sorted 13 | func ConvertMapToDoc(inputMap map[string]interface{}) bson.D { 14 | result := make(bson.D, len(inputMap)) 15 | 16 | // making sorted array of map keys 17 | //-------------------------------- 18 | sortedKeys := make([]string, len(inputMap)) 19 | var idx = 0 20 | for key := range inputMap { 21 | sortedKeys[idx] = key 22 | idx++ 23 | } 24 | sort.Strings(sortedKeys) 25 | 26 | // converting key values to bson.DocElem 27 | for _, key := range sortedKeys { 28 | var docValue interface{} = inputMap[key] 29 | if mapValue, ok := docValue.(map[string]interface{}); ok { 30 | docValue = ConvertMapToDoc(mapValue) 31 | } 32 | result = append(result, bson.DocElem{Name: key, Value: docValue}) 33 | } 34 | 35 | return result 36 | } 37 | 38 | // BsonDToString converts bson.D to readable form, mostly used for debug 39 | func BsonDToString(input bson.D) string { 40 | result := "" 41 | 42 | result += "{" 43 | for _, bsonItem := range input { 44 | result += "'" + bsonItem.Name + "': " 45 | 46 | switch typedValue := bsonItem.Value.(type) { 47 | case []bson.D: 48 | result += "[" 49 | 50 | addComaFlag := false 51 | for _, valueItem := range typedValue { 52 | if addComaFlag { 53 | result += ", " 54 | } else { 55 | addComaFlag = true 56 | } 57 | result += BsonDToString(valueItem) 58 | } 59 | 60 | result += "]" 61 | case bson.D: 62 | result += BsonDToString(typedValue) 63 | case time.Time: 64 | result += "ISODate(\"" + typedValue.Format(time.RFC3339) + "\")" 65 | default: 66 | if bsonItem.Value == nil { 67 | result += "null" 68 | } 69 | result += utils.InterfaceToString(typedValue) 70 | } 71 | } 72 | result += "}" 73 | 74 | return result 75 | } 76 | -------------------------------------------------------------------------------- /db/mysql/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Ottemo. All rights reserved. 2 | 3 | /* 4 | Package mysql is a default MySQL implementation for Ottemo. It provides "InterfaceDBEngine" implementation declared in 5 | "github.com/ottemo/commerce/db" package. 6 | 7 | Package stands on a top of "github.com/go-sql-driver/mysql" package. 8 | 9 | */ 10 | package mysql 11 | -------------------------------------------------------------------------------- /db/mysql/init.go: -------------------------------------------------------------------------------- 1 | package mysql 2 | 3 | import ( 4 | "github.com/ottemo/commerce/db" 5 | "github.com/ottemo/commerce/env" 6 | ) 7 | 8 | // init makes package self-initialization routine 9 | func init() { 10 | dbEngine = new(DBEngine) 11 | dbEngine.attributeTypes = make(map[string]map[string]string) 12 | 13 | var _ db.InterfaceDBEngine = dbEngine 14 | 15 | var dbConnector = db.NewDBConnector(dbEngine) 16 | env.RegisterOnConfigIniStart(dbConnector.ConnectAsync) 17 | 18 | if err := db.RegisterDBEngine(dbEngine); err != nil { 19 | _ = env.ErrorDispatch(err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /db/postgres/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Ottemo Authors. All rights reserved. 2 | 3 | /* 4 | Package postgres is a default Postgres implementation for Ottemo. It provides "InterfaceDBEngine" implementation declared in 5 | "github.com/ottemo/commerce/db" package. 6 | 7 | Package stands on a top of "github.com/go-sql-driver/postgres" package. 8 | 9 | */ 10 | package postgres 11 | -------------------------------------------------------------------------------- /db/postgres/init.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "github.com/ottemo/commerce/db" 5 | "github.com/ottemo/commerce/env" 6 | 7 | // Postgres driver for the database/sql package 8 | _ "github.com/lib/pq" 9 | ) 10 | 11 | // init makes package self-initialization routine 12 | func init() { 13 | dbEngine = new(DBEngine) 14 | dbEngine.attributeTypes = make(map[string]map[string]string) 15 | 16 | var _ db.InterfaceDBEngine = dbEngine 17 | 18 | var dbConnector = db.NewDBConnector(dbEngine) 19 | env.RegisterOnConfigIniStart(dbConnector.ConnectAsync) 20 | 21 | if err := db.RegisterDBEngine(dbEngine); err != nil { 22 | _ = env.ErrorDispatch(err) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /db/postgres/postgres_test.go: -------------------------------------------------------------------------------- 1 | package postgres 2 | 3 | import ( 4 | "testing" 5 | "github.com/ottemo/commerce/db" 6 | _ "github.com/lib/pq" 7 | ) 8 | 9 | func TestSimple(t *testing.T) { 10 | var dbConnector = db.NewDBConnector(dbEngine) 11 | if err := dbConnector.Connect(); err != nil { 12 | t.Fatal(err) 13 | } 14 | db.TestSimple(t) 15 | } 16 | -------------------------------------------------------------------------------- /db/sqlite/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Ottemo. All rights reserved. 2 | 3 | /* 4 | Package sqlite is a default SQLite implementation for Ottemo. It provides "InterfaceDBEngine" implementation declared in 5 | "github.com/ottemo/commerce/db" package. 6 | 7 | Package stands on a top of "github.com/mxk/go-sqlite/sqlite3" package. 8 | 9 | SQLite is a software library that implements a self-contained, serverless, zero-configuration, transactional SQL database 10 | engine. SQLite is the most widely deployed SQL database engine in the world. The source code for SQLite is in the public 11 | domain. 12 | */ 13 | package sqlite 14 | -------------------------------------------------------------------------------- /db/sqlite/init.go: -------------------------------------------------------------------------------- 1 | package sqlite 2 | 3 | import ( 4 | "github.com/ottemo/commerce/db" 5 | "github.com/ottemo/commerce/env" 6 | ) 7 | 8 | // init makes package self-initialization routine 9 | func init() { 10 | dbEngine = new(DBEngine) 11 | dbEngine.attributeTypes = make(map[string]map[string]string) 12 | 13 | var _ db.InterfaceDBEngine = dbEngine 14 | 15 | var dbConnector = db.NewDBConnector(dbEngine) 16 | env.RegisterOnConfigIniStart(dbConnector.ConnectAsync) 17 | 18 | if err := db.RegisterDBEngine(dbEngine); err != nil { 19 | _ = env.ErrorDispatch(err) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /env/config/decl.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | ) 6 | 7 | // Package global constants 8 | const ( 9 | ConstCollectionNameConfig = "config" 10 | 11 | ConstErrorModule = "env/config" 12 | ConstErrorLevel = env.ConstErrorLevelService 13 | ) 14 | 15 | // DefaultConfig is a default implementer of InterfaceConfig 16 | type DefaultConfig struct { 17 | configValues map[string]interface{} 18 | configTypes map[string]string 19 | configValidators map[string]env.FuncConfigValueValidator 20 | } 21 | -------------------------------------------------------------------------------- /env/config/i_impex_model.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/ottemo/commerce/utils" 5 | ) 6 | 7 | // Import imports value in config through Impex 8 | func (it *DefaultConfig) Import(item map[string]interface{}, testMode bool) (map[string]interface{}, error) { 9 | var path string 10 | var value interface{} 11 | 12 | if pathValue, present := item["path"]; present { 13 | path = utils.InterfaceToString(pathValue) 14 | } else if keyValue, present := item["key"]; present { 15 | path = utils.InterfaceToString(keyValue) 16 | } 17 | 18 | value, _ = item["value"] 19 | 20 | if testMode == false { 21 | err := it.SetValue(path, value) 22 | return item, err 23 | } 24 | 25 | return item, nil 26 | } 27 | 28 | // Export exports config values through Impex 29 | func (it *DefaultConfig) Export(iterator func(map[string]interface{}) bool) error { 30 | for itemPath, itemValue := range it.configValues { 31 | continueFlag := iterator(map[string]interface{}{"path": itemPath, "value": itemValue}) 32 | if continueFlag == false { 33 | break 34 | } 35 | } 36 | 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /env/config/init.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | "github.com/ottemo/commerce/db" 6 | "github.com/ottemo/commerce/env" 7 | "github.com/ottemo/commerce/impex" 8 | ) 9 | 10 | // init makes package self-initialization routine 11 | func init() { 12 | instance := &DefaultConfig{ 13 | configValues: make(map[string]interface{}), 14 | configTypes: make(map[string]string), 15 | configValidators: make(map[string]env.FuncConfigValueValidator)} 16 | 17 | db.RegisterOnDatabaseStart(setupDB) 18 | db.RegisterOnDatabaseStart(instance.Load) 19 | 20 | api.RegisterOnRestServiceStart(setupAPI) 21 | 22 | if err := env.RegisterConfig(instance); err != nil { 23 | _ = env.ErrorDispatch(err) 24 | } 25 | 26 | if err := impex.RegisterImpexModel("Config", instance); err != nil { 27 | _ = env.ErrorDispatch(err) 28 | } 29 | } 30 | 31 | // setupDB prepares system database for package usage 32 | func setupDB() error { 33 | collection, err := db.GetCollection(ConstCollectionNameConfig) 34 | if err != nil { 35 | return env.ErrorDispatch(err) 36 | } 37 | 38 | if err := collection.AddColumn("path", db.ConstTypeVarchar, true); err != nil { 39 | return env.ErrorDispatch(err) 40 | } 41 | if err := collection.AddColumn("value", db.ConstTypeText, false); err != nil { 42 | return env.ErrorDispatch(err) 43 | } 44 | 45 | if err := collection.AddColumn("type", db.ConstTypeVarchar, false); err != nil { 46 | return env.ErrorDispatch(err) 47 | } 48 | 49 | if err := collection.AddColumn("editor", db.ConstTypeVarchar, false); err != nil { 50 | return env.ErrorDispatch(err) 51 | } 52 | if err := collection.AddColumn("options", db.ConstTypeText, false); err != nil { 53 | return env.ErrorDispatch(err) 54 | } 55 | 56 | if err := collection.AddColumn("label", db.ConstTypeVarchar, false); err != nil { 57 | return env.ErrorDispatch(err) 58 | } 59 | if err := collection.AddColumn("description", db.ConstTypeText, false); err != nil { 60 | return env.ErrorDispatch(err) 61 | } 62 | 63 | if err := collection.AddColumn("image", db.ConstTypeVarchar, false); err != nil { 64 | return env.ErrorDispatch(err) 65 | } 66 | 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /env/cron/decl.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gorhill/cronexpr" 7 | "github.com/ottemo/commerce/env" 8 | ) 9 | 10 | // Package global constants 11 | const ( 12 | ConstErrorModule = "env/cron" 13 | ConstErrorLevel = env.ConstErrorLevelService 14 | ) 15 | 16 | // DefaultCronScheduler is a default implementer of InterfaceIniConfig 17 | type DefaultCronScheduler struct { 18 | tasks map[string]env.FuncCronTask 19 | schedules []*DefaultCronSchedule 20 | 21 | appStarted bool 22 | } 23 | 24 | // DefaultCronSchedule structure to hold schedule information (for internal usage) 25 | type DefaultCronSchedule struct { 26 | CronExpr string 27 | TaskName string 28 | Params map[string]interface{} 29 | Repeat bool 30 | Time time.Time 31 | active bool 32 | 33 | task env.FuncCronTask 34 | expr *cronexpr.Expression 35 | 36 | scheduler *DefaultCronScheduler 37 | } 38 | -------------------------------------------------------------------------------- /env/cron/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Ottemo, All rights reserved. 2 | 3 | /* 4 | Package cron is a utility to schedule tasks. These tasks maybe scheduled for 5 | a specific time, they may be repeatable or intended to be run immediately. 6 | There are several API Endpoints which allow a developer to interact with the 7 | scheduling system. 8 | 9 | It is important to understand several concepts. 10 | 11 | Task - a job which can be scheduled to run at a specific time 12 | Schedule - a listing of all active tasks, when they will be executed and their respective metadata 13 | 14 | The API allows you to: 15 | * Obtain a list of the currently scheduled tasks 16 | * Create a task to be run on a schedule 17 | * Obtain a list of possible tasks to be scheduled 18 | * Enable a task to be run on a schedule 19 | * Disable a task 20 | * Update the specified task 21 | * Run the specified task now 22 | 23 | //TODO: add link to api documentation 24 | 25 | */ 26 | package cron 27 | -------------------------------------------------------------------------------- /env/cron/init.go: -------------------------------------------------------------------------------- 1 | package cron 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | "github.com/ottemo/commerce/app" 6 | "github.com/ottemo/commerce/env" 7 | ) 8 | 9 | // init makes package self-initialization routine 10 | func init() { 11 | instance := new(DefaultCronScheduler) 12 | var _ env.InterfaceScheduler = instance 13 | var _ env.InterfaceSchedule = new(DefaultCronSchedule) 14 | 15 | instance.tasks = make(map[string]env.FuncCronTask) 16 | instance.schedules = make([]*DefaultCronSchedule, 0) 17 | 18 | app.OnAppInit(instance.appInitEvent) 19 | app.OnAppEnd(instance.appEndEvent) 20 | api.RegisterOnRestServiceStart(setupAPI) 21 | 22 | if err := env.RegisterScheduler(instance); err != nil { 23 | _ = env.ErrorDispatch(err) 24 | } 25 | } 26 | 27 | // routines before application end 28 | func (it *DefaultCronScheduler) appEndEvent() error { 29 | return nil 30 | } 31 | 32 | // routines before application start (on init phase) 33 | func (it *DefaultCronScheduler) appInitEvent() error { 34 | 35 | // TODO: load manually specified tasks from DB 36 | 37 | it.appStarted = true 38 | 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /env/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Ottemo. All rights reserved. 2 | 3 | /* 4 | Package env represents environment services abstraction layer. It provides a set of interfaces and helpers to make 5 | application input/output operations and messaging behind scenes. 6 | 7 | For a meantime package consists of: 8 | - configuration service 9 | - ini file service 10 | - error bus 11 | - event bus 12 | - logger 13 | */ 14 | package env 15 | -------------------------------------------------------------------------------- /env/errorbus/decl.go: -------------------------------------------------------------------------------- 1 | package errorbus 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | "regexp" 6 | ) 7 | 8 | // Package global constants 9 | const ( 10 | ConstCollectCallStack = true // flag to indicate that call stack information within error is required 11 | 12 | ConstConfigPathError = "general.error" 13 | ConstConfigPathErrorHideLevel = "general.error.hide_level" 14 | ConstConfigPathErrorHideMessage = "general.error.hide_message" 15 | 16 | ConstErrorModule = "env/errorbus" 17 | ConstErrorLevel = env.ConstErrorLevelService 18 | ) 19 | 20 | // Package global variables 21 | var ( 22 | // ConstMsgRegexp is a regular expression used to parse error message 23 | ConstMsgRegexp = regexp.MustCompile(`^[\[{(]?\s*(?:(?:([a-zA-Z_\/-]+)?[:])?([0-9]+)?[-: ]([0-9a-fA-F-]+)?)?\s*[\]})]?\s*[:\->]*\s*(.+)`) 24 | 25 | debug = true 26 | hideLevel = 5 27 | hideMessage = "System error has occured" 28 | ) 29 | 30 | // DefaultErrorBus InterfaceErrorBus implementer class 31 | type DefaultErrorBus struct { 32 | listeners []env.FuncErrorListener 33 | } 34 | 35 | // OttemoError @reconcile@ InterfaceOttemoError implementer class 36 | type OttemoError struct { 37 | Message string 38 | Module string 39 | Code string 40 | Level int 41 | 42 | CallStack string 43 | 44 | handled bool 45 | logged bool 46 | } 47 | -------------------------------------------------------------------------------- /env/errorbus/i_ottemo_error.go: -------------------------------------------------------------------------------- 1 | package errorbus 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // returns error message only 8 | func (it *OttemoError) Error() string { 9 | if it.Level < hideLevel { 10 | return hideMessage 11 | } 12 | return it.Message 13 | } 14 | 15 | // ErrorMessage returns original error message 16 | func (it *OttemoError) ErrorMessage() string { 17 | return it.Message 18 | } 19 | 20 | // ErrorFull returns error detail information about error 21 | func (it *OttemoError) ErrorFull() string { 22 | message := it.Message 23 | if it.CallStack != "" { 24 | message += "\n" + it.CallStack 25 | } 26 | 27 | module := it.Module 28 | if module != "" { 29 | module += ":" 30 | } 31 | return fmt.Sprintf("%s%d:%s - %s", module, it.Level, it.Code, message) 32 | } 33 | 34 | // ErrorLevel returns error level - if specified or 0 35 | func (it *OttemoError) ErrorLevel() int { 36 | return it.Level 37 | } 38 | 39 | // ErrorCode returns error code (hexadecimal value) if specified, otherwise MD5 over error message 40 | func (it *OttemoError) ErrorCode() string { 41 | return it.Code 42 | } 43 | 44 | // ErrorCallStack returns error functions call stack for error 45 | // Note: ConstCollectStack constant should be set to true, otherwise, stack information will be blank 46 | func (it *OttemoError) ErrorCallStack() string { 47 | return it.CallStack 48 | } 49 | 50 | // IsHandled returns handled flag 51 | func (it *OttemoError) IsHandled() bool { 52 | return it.handled 53 | } 54 | 55 | // MarkHandled makes error as already processed (prevents from future processing) 56 | func (it *OttemoError) MarkHandled() bool { 57 | it.handled = true 58 | return it.handled 59 | } 60 | 61 | // IsLogged returns logged flag 62 | func (it *OttemoError) IsLogged() bool { 63 | return it.logged 64 | } 65 | 66 | // MarkLogged makes error as already logged (prevents from future logging) 67 | func (it *OttemoError) MarkLogged() bool { 68 | it.logged = true 69 | return it.logged 70 | } 71 | -------------------------------------------------------------------------------- /env/errorbus/init.go: -------------------------------------------------------------------------------- 1 | package errorbus 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | "github.com/ottemo/commerce/utils" 6 | ) 7 | 8 | // init makes package self-initialization routine 9 | func init() { 10 | instance := &DefaultErrorBus{listeners: make([]env.FuncErrorListener, 0)} 11 | var _ env.InterfaceErrorBus = instance 12 | 13 | var _ env.InterfaceOttemoError = new(OttemoError) 14 | 15 | if err := env.RegisterErrorBus(instance); err != nil { 16 | _ = env.ErrorDispatch(err) 17 | } 18 | env.RegisterOnConfigIniStart(setupOnIniConfigStart) 19 | env.RegisterOnConfigStart(setupConfig) 20 | } 21 | 22 | // setupOnIniConfigStart is a initialization based on ini config service 23 | func setupOnIniConfigStart() error { 24 | 25 | if iniConfig := env.GetIniConfig(); iniConfig != nil { 26 | if iniValue := iniConfig.GetValue("error.instant.debug", "true"); iniValue != "" { 27 | debug = utils.InterfaceToBool(iniValue) 28 | } 29 | } 30 | 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /env/eventbus/decl.go: -------------------------------------------------------------------------------- 1 | package eventbus 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | ) 6 | 7 | // DefaultEventBus InterfaceEventBus implementer class 8 | type DefaultEventBus struct { 9 | listeners map[string][]env.FuncEventListener 10 | } 11 | -------------------------------------------------------------------------------- /env/eventbus/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Ottemo. All rights reserved. 2 | 3 | /* 4 | Package eventbus is a default implementation of InterfaceEventBus declared in "github.com/ottemo/commerce/env" package. 5 | 6 | Event bus is a service used for simplified communication between application code. Event provider emits an event message 7 | and event listeners makes special handling for an event. 8 | 9 | Event name is "." delimited string. So, even listeners can listen for all messages of "top level" message (i.e. listener 10 | for "api" event will listen for "api.checkout.visitCheckout" automatically). 11 | 12 | Event provides a data objects relative to. These objects could be changed during event handling, as well as new data 13 | could be added to a data map, during event processing. 14 | 15 | To be more consistent and clear, event names should be declared as a package constants with description about providing 16 | event data map. 17 | 18 | Example 1: 19 | ---------- 20 | return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "004e9f7b-bb97-4356-bbc2-5e084736983b", "unknown cmd '"+args[0]+"'") 21 | env.Event("api.checkout.visitCheckout", eventData) 22 | 23 | Example 2: 24 | ---------- 25 | salesHandler := func(event string, eventData map[string]interface{}) bool { 26 | env.LogMessage( fmt.Sprintf("%+v", eventData) ) 27 | } 28 | env.EventRegisterListener("checkout.success", salesHandler) 29 | */ 30 | package eventbus 31 | -------------------------------------------------------------------------------- /env/eventbus/i_event_bus.go: -------------------------------------------------------------------------------- 1 | package eventbus 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | ) 6 | 7 | // RegisterListener adds listener to event handling stack 8 | // - event listening is patch based, "" - global listener on any event, "api.product" - will listen for app events starts with api.product.[...]) 9 | func (it *DefaultEventBus) RegisterListener(event string, listener env.FuncEventListener) { 10 | if value, present := it.listeners[event]; present { 11 | it.listeners[event] = append(value, listener) 12 | } else { 13 | it.listeners[event] = []env.FuncEventListener{listener} 14 | } 15 | } 16 | 17 | // New generates new event, with following dispatching 18 | func (it *DefaultEventBus) New(event string, args map[string]interface{}) { 19 | 20 | // loop over top level events 21 | // (i.e. "api.checkout.success" event will notify following listeners: "", "api", "api.checkout", "api.checkout.success") 22 | lastChar := len(event) - 1 23 | for charIdx, char := range event { 24 | if charIdx == 0 || charIdx == lastChar || char == '.' { 25 | levelEvent := event 26 | if charIdx != lastChar { 27 | levelEvent = event[0:charIdx] 28 | } 29 | 30 | // processing listeners withing level if present 31 | if listeners, present := it.listeners[levelEvent]; present { 32 | for _, listener := range listeners { 33 | 34 | // processing listener, if it wants to stop handling - doing this 35 | if listener(event, args) == false { 36 | return 37 | } 38 | 39 | } 40 | } 41 | } 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /env/eventbus/init.go: -------------------------------------------------------------------------------- 1 | package eventbus 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | ) 6 | 7 | // init makes package self-initialization routine 8 | func init() { 9 | instance := new(DefaultEventBus) 10 | instance.listeners = make(map[string][]env.FuncEventListener) 11 | 12 | var _ env.InterfaceEventBus = instance 13 | 14 | if err := env.RegisterEventBus(instance); err != nil { 15 | _ = env.ErrorDispatch(err) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /env/ini/decl.go: -------------------------------------------------------------------------------- 1 | package ini 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | ) 6 | 7 | // Package global constants 8 | const ( 9 | ConstIniGlobalSection = "" // ini file section name to be used as default section 10 | ConstAskForValuePrefix = "?" // prefix used before default ini value to be asked in console if not set 11 | 12 | ConstCmdArgStoreAllFlag = "--iniStoreAll" 13 | ConstCmdArgSectionName = "--iniSection=" 14 | ConstCmdArgTestFlag = "--test" 15 | 16 | ConstEnvironmentIniFile = "OTTEMO_INI" 17 | ConstEnvironmentIniSection = "OTTEMO_MODE" 18 | 19 | ConstTestSectionName = "test" 20 | ConstDefaultIniFile = "ottemo.ini" 21 | ConstBackupFileSuffix = ".bak" 22 | 23 | ConstErrorModule = "env/ini" 24 | ConstErrorLevel = env.ConstErrorLevelService 25 | ) 26 | 27 | // DefaultIniConfig is a default implementer of InterfaceIniConfig 28 | type DefaultIniConfig struct { 29 | iniFilePath string 30 | 31 | iniFileValues map[string]map[string]string 32 | currentSection string 33 | 34 | keysToStore map[string]bool 35 | storeAll bool 36 | } 37 | -------------------------------------------------------------------------------- /env/ini/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Ottemo. All rights reserved. 2 | 3 | /* 4 | Package ini is a default implementation of InterfaceIniConfig declared in "github.com/ottemo/commerce/env" package. 5 | 6 | Ini config is a config.ini file where startup information located. Ini file could be separated on a sections (refer to 7 | https://github.com/vaughan0/go-ini) for ini file lookup. Ini config takes a value from ini file "current" section and 8 | if not found in there - looking for a global section. 9 | 10 | Special section [ConstTestSectionName] ("test") used for "test mode" application startup. To start application in that 11 | mode [ConstCmdArgTestFlag] "--test" should be used. 12 | 13 | Example 1: 14 | ---------- 15 | if iniConfig := env.GetIniConfig(); iniConfig != nil { 16 | if iniValue := iniConfig.GetValue("db.sqlite3.uri", uri); iniValue != "" { 17 | uri = iniValue 18 | } 19 | } 20 | 21 | Example 2: 22 | ---------- 23 | uri := env.IniValue("db.sqlite3.uri") 24 | */ 25 | package ini 26 | -------------------------------------------------------------------------------- /env/logger/config.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "errors" 5 | "github.com/ottemo/commerce/env" 6 | "github.com/ottemo/commerce/utils" 7 | ) 8 | 9 | // setupConfig setups package configuration values for a system 10 | func setupConfig() error { 11 | config := env.GetConfig() 12 | if config == nil { 13 | err := env.ErrorNew(ConstErrorModule, env.ConstErrorLevelStartStop, "6dee39ac-c930-420e-b777-b95f6cab8981", "can't obtain config") 14 | return env.ErrorDispatch(err) 15 | } 16 | 17 | err := config.RegisterItem(env.StructConfigItem{ 18 | Path: ConstConfigPathError, 19 | Value: nil, 20 | Type: env.ConstConfigTypeGroup, 21 | Editor: "", 22 | Options: nil, 23 | Label: "Error", 24 | Description: "error handling settings", 25 | Image: "", 26 | }, nil) 27 | 28 | if err != nil { 29 | return env.ErrorDispatch(err) 30 | } 31 | 32 | // Log level 33 | logLevelValidator := func(newValue interface{}) (interface{}, error) { 34 | newLevel := utils.InterfaceToInt(newValue) 35 | if newLevel > 10 || newLevel < 0 { 36 | err := errors.New("'Log level' config value should be between 0 and 10") 37 | return errorLogLevel, env.ErrorDispatch(err) 38 | } 39 | errorLogLevel = newLevel 40 | 41 | return errorLogLevel, nil 42 | } 43 | err = config.RegisterItem(env.StructConfigItem{ 44 | Path: ConstConfigPathErrorLogLevel, 45 | Value: 5, 46 | Type: env.ConstConfigTypeInteger, 47 | Editor: "integer", 48 | Options: nil, 49 | Label: "Log level", 50 | Description: "errors below specified level will be send to logger service", 51 | Image: "", 52 | }, logLevelValidator) 53 | 54 | if err != nil { 55 | return env.ErrorDispatch(err) 56 | } 57 | 58 | return nil 59 | } 60 | -------------------------------------------------------------------------------- /env/logger/decl.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import "github.com/ottemo/commerce/env" 4 | 5 | // Package global constants 6 | const ( 7 | ConstCollectCallStack = true // flag to indicate that call stack information within error is required 8 | 9 | ConstConfigPathError = "general.error" 10 | ConstConfigPathErrorLogLevel = "general.error.log_level" 11 | 12 | ConstErrorModule = "env/logger" 13 | ConstErrorLevel = env.ConstErrorLevelService 14 | ) 15 | 16 | // Package global variables 17 | var ( 18 | baseDirectory = "./var/log/" // folder location where to store logs 19 | defaultLogFile = "system.log" // filename for default log 20 | defaultErrorsFile = "errors.log" // filename for errors log 21 | 22 | errorLogLevel = 5 23 | ) 24 | 25 | // DefaultLogger is a default implementer of InterfaceLogger 26 | type DefaultLogger struct{} 27 | -------------------------------------------------------------------------------- /env/logger/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Ottemo. All rights reserved. 2 | 3 | /* 4 | Package logger is a default implementation of InterfaceLogger declared in "github.com/ottemo/commerce/env" package. 5 | 6 | Default logger is pretty simple implementation, which takes an message and puts it into a "storage" (a file with specific 7 | name in this case). If for some reason message can not be places in file (file access denied, etc.) message will be 8 | printed to stdout. Message time (in RFC3339 format) and specified prefix adds to message before output. 9 | */ 10 | package logger 11 | -------------------------------------------------------------------------------- /env/logger/init.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "os" 5 | "fmt" 6 | 7 | "github.com/ottemo/commerce/env" 8 | ) 9 | 10 | // init makes package self-initialization routine 11 | func init() { 12 | instance := new(DefaultLogger) 13 | var _ env.InterfaceLogger = instance 14 | 15 | if err := env.RegisterLogger(instance); err != nil { 16 | fmt.Println(err.Error()) 17 | } 18 | env.RegisterOnConfigIniStart(startup) 19 | env.RegisterOnConfigStart(setupConfig) 20 | } 21 | 22 | // startup is a service pre-initialization stuff 23 | func startup() error { 24 | if _, err := os.Stat(baseDirectory); !os.IsExist(err) { 25 | err := os.MkdirAll(baseDirectory, os.ModePerm) 26 | if err != nil { 27 | return err 28 | } 29 | } 30 | 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /foundation.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name commerce.dev.ottemo.io; 4 | 5 | location / { 6 | proxy_pass http://127.0.0.1:3000 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /impex/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Ottemo. All rights reserved. 2 | 3 | /* 4 | Package impex represents import/export service. It provides a set of interfaces and helpers for extending it's functionality 5 | as well as implements basic ideas of import/export functionality. 6 | 7 | Importing and exporting happens through CSV files you should specify within API request in order to ue this service. CSV file 8 | converts into map[string]interface{} objects which applies to model instances. 9 | 10 | Refer to "Ottemo Import/Export Manual" for detail on service usage. 11 | */ 12 | package impex 13 | -------------------------------------------------------------------------------- /impex/helpers.go: -------------------------------------------------------------------------------- 1 | package impex 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | "github.com/ottemo/commerce/env" 6 | ) 7 | 8 | // ImportStartHandler is a middleware to init importState for async "next" procedure. 9 | func ImportStartHandler(next api.FuncAPIHandler) api.FuncAPIHandler { 10 | return func(context api.InterfaceApplicationContext) (interface{}, error) { 11 | if importStatus.state != constImportStateIdle { 12 | additionalMessage := "" 13 | if importStatus.file != nil { 14 | additionalMessage = " Currently processing " + importStatus.file.name 15 | } 16 | return nil, env.ErrorNew(ConstErrorModule, env.ConstErrorLevelAPI, "4bec46b6-b6b0-4821-8978-44d0f051750d", "Another import is in progres." + additionalMessage) 17 | } 18 | 19 | importStatus.state = constImportStateProcessing 20 | delete(importStatus.sessions, context.GetSession().GetID()) 21 | return next(context) 22 | } 23 | } 24 | 25 | // ImportResultHandler will process import call's result 26 | // It return no values, because of async handler result processing. 27 | var ImportResultHandler = func(context api.InterfaceApplicationContext, result interface{}, err error) { 28 | importStatus.state = constImportStateIdle 29 | 30 | importStatus.sessions[context.GetSession().GetID()] = map[string]interface{}{ 31 | "result": result, 32 | "err": err, 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /impex/interfaces.go: -------------------------------------------------------------------------------- 1 | package impex 2 | 3 | // InterfaceImpexImportCmd is an interface used to work with registered import commands 4 | type InterfaceImpexImportCmd interface { 5 | Init(args []string, exchange map[string]interface{}) error 6 | Process(itemData map[string]interface{}, input interface{}, exchange map[string]interface{}) (interface{}, error) 7 | Test(itemData map[string]interface{}, input interface{}, exchange map[string]interface{}) (interface{}, error) 8 | } 9 | 10 | // InterfaceImpexModel is an interface model should implement to make it work with impex service 11 | type InterfaceImpexModel interface { 12 | Import(item map[string]interface{}, testMode bool) (map[string]interface{}, error) 13 | Export(func(map[string]interface{}) bool) error 14 | } 15 | -------------------------------------------------------------------------------- /impex/manager.go: -------------------------------------------------------------------------------- 1 | package impex 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | ) 6 | 7 | // RegisterImportCommand registers new command to import/export system 8 | func RegisterImportCommand(commandName string, command InterfaceImpexImportCmd) error { 9 | if _, present := importCmd[commandName]; present { 10 | return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "f4f50cfc-3de6-4c76-8518-02ad26790f44", commandName+" already registered in impex") 11 | } 12 | 13 | importCmd[commandName] = command 14 | 15 | return nil 16 | } 17 | 18 | // UnRegisterImportCommand un-registers command from import/export system 19 | func UnRegisterImportCommand(commandName string) error { 20 | if _, present := importCmd[commandName]; !present { 21 | return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "a9df49e3-0d06-4afa-8e3a-7cde5a3b2a1d", "can't find registered command "+commandName) 22 | } 23 | 24 | delete(importCmd, commandName) 25 | 26 | return nil 27 | } 28 | 29 | // RegisterImpexModel registers model instance which supports InterfaceImpexModel interface to import/export system 30 | func RegisterImpexModel(name string, instance InterfaceImpexModel) error { 31 | if _, present := impexModels[name]; present { 32 | return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "8c5dba4f-6c54-4551-931a-a60d581252ab", name+" model already registered in impex") 33 | } 34 | 35 | impexModels[name] = instance 36 | 37 | return nil 38 | } 39 | 40 | // UnRegisterImpexModel un-registers InterfaceImpexModel capable model from import/export system 41 | func UnRegisterImpexModel(name string) error { 42 | if _, present := impexModels[name]; !present { 43 | return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "0b0863dd-f8ba-4a61-8438-603e649189e1", "can't find registered model "+name) 44 | } 45 | 46 | delete(impexModels, name) 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | "time" 9 | 10 | "github.com/ottemo/commerce/app" 11 | 12 | // using standard set of packages 13 | _ "github.com/ottemo/commerce/basebuild" 14 | "github.com/ottemo/commerce/env" 15 | ) 16 | 17 | func init() { 18 | // time.Unix() should be in UTC (as it could be not by default) 19 | time.Local = time.UTC 20 | } 21 | 22 | // executable file start point 23 | func main() { 24 | defer func() { 25 | if err := app.End(); err != nil { // application close event 26 | fmt.Println(err.Error()) 27 | } 28 | }() 29 | 30 | // we should intercept os signals to application as we should call app.End() before 31 | signalChain := make(chan os.Signal, 1) 32 | signal.Notify(signalChain, os.Interrupt, syscall.SIGTERM) 33 | go func() { 34 | for _ = range signalChain { 35 | err := app.End() 36 | if err != nil { 37 | _ = env.ErrorDispatch(err) 38 | fmt.Println(err.Error()) 39 | } 40 | 41 | os.Exit(0) 42 | } 43 | }() 44 | 45 | // application start event 46 | if err := app.Start(); err != nil { 47 | _ = env.ErrorDispatch(err) 48 | fmt.Println(err.Error()) 49 | os.Exit(0) 50 | } 51 | 52 | fmt.Println("Ottemo " + app.GetVerboseVersion()) 53 | 54 | // starting HTTP server 55 | if err := app.Serve(); err != nil { 56 | fmt.Println(err.Error()) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /media/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Ottemo. All rights reserved. 2 | 3 | /* 4 | Package media represents media storage abstraction layer. It provides a set of interfaces and helpers to interact with 5 | media-storage services. 6 | 7 | If package decides to have media files related to model instances it should refer to that library for implementation 8 | of media storage list/read/write operations. 9 | 10 | Providing Ottemo with a new media storage supposing "InterfaceMediaStorage" implementation with following registration 11 | for media package. 12 | */ 13 | package media 14 | -------------------------------------------------------------------------------- /media/fsmedia/api.go: -------------------------------------------------------------------------------- 1 | package fsmedia 2 | 3 | import ( 4 | "github.com/ottemo/commerce/api" 5 | "github.com/ottemo/commerce/media" 6 | "github.com/ottemo/commerce/utils" 7 | "strings" 8 | ) 9 | 10 | // configures package related API endpoint routines 11 | func setupAPI() error { 12 | 13 | // api.GetRestService().GET("media/resizeAll", api.IsAdminHandler(APIResizeAll)) 14 | api.GetRestService().GET("media/*path", APIGetMedia) 15 | 16 | return nil 17 | } 18 | 19 | func APIGetMedia(context api.InterfaceApplicationContext) (interface{}, error) { 20 | path := context.GetRequestArgument("path") 21 | 22 | mediaStorage, err := media.GetMediaStorage() 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | result, err := mediaStorage.GetMediaByPath(path) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | // ref to https://www.sitepoint.com/mime-types-complete-list/ 33 | if strings.HasSuffix(path, ".jpg") { 34 | context.SetResponseContentType("image/jpeg") 35 | } else if strings.HasSuffix(path, ".png") { 36 | context.SetResponseContentType("image/png") 37 | } else if strings.HasSuffix(path, ".avi") { 38 | context.SetResponseContentType("image/avi") 39 | } else if strings.HasSuffix(path, ".txt") { 40 | context.SetResponseContentType("text/plain") 41 | } else { 42 | context.SetResponseContentType("application/octet-stream") 43 | } 44 | 45 | return result, nil 46 | } 47 | 48 | // APIGetMediaInfo will resize all images if the params of the request contain 'resizeAll' with a value of true 49 | func APIResizeAll(context api.InterfaceApplicationContext) (interface{}, error) { 50 | // TODO: add example api call or add this to Apiary - jwv 51 | 52 | requestParams := context.GetRequestArguments() 53 | resizeAll := utils.GetFirstMapValue(requestParams, "resizeAll", "resizeImages", "resizeAllImages") 54 | 55 | if resizeAll != nil && utils.InterfaceToBool(resizeAll) { 56 | mediaStorage, err := media.GetMediaStorage() 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | err = mediaStorage.ResizeAllMediaImages() 62 | if err != nil { 63 | return nil, err 64 | } 65 | } 66 | 67 | return "ok", nil 68 | } 69 | -------------------------------------------------------------------------------- /media/fsmedia/decl.go: -------------------------------------------------------------------------------- 1 | package fsmedia 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | ) 6 | 7 | // Package global constants 8 | const ( 9 | ConstMediaDBCollection = "media" // database collection name to store media assignment information into 10 | ConstMediaDefaultFolder = "./media/" // filesystem folder path to store media files in there 11 | 12 | ConstResizeOnBackground = true 13 | 14 | ConstDefaultImageSize = "1000x1000" // "800x400" 15 | ConstDefaultImageSizes = "thumb: 280x350" // "small: 75x75, thumb: 260x300, big: 560x650" 16 | 17 | ConstConfigPathMediaImageSize = "general.app.image_size" // base image size 18 | ConstConfigPathMediaImageSizes = "general.app.image_sizes" // other image sizes required 19 | 20 | ConstConfigPathMediaBaseURL = "general.app.media_base_url" 21 | 22 | ConstErrorModule = "media/fsmedia" 23 | ConstErrorLevel = env.ConstErrorLevelService 24 | ) 25 | 26 | // resizeImagesOnFly can be specified in ini config file by key "media.resize.images.onfly", false by default 27 | var ( 28 | resizeImagesOnFly bool 29 | mediaBasePath = "media" 30 | ) 31 | 32 | // FilesystemMediaStorage is a filesystem based implementer of InterfaceMediaStorage 33 | type FilesystemMediaStorage struct { 34 | storageFolder string 35 | setupWaitCnt int 36 | 37 | baseSize string 38 | biggestSize string 39 | imageSizes map[string]string 40 | } 41 | -------------------------------------------------------------------------------- /media/fsmedia/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Ottemo. All rights reserved. 2 | 3 | /* 4 | Package fsmedia is a default implementation of InterfaceMediaStorage declared in "github.com/ottemo/commerce/media" package. 5 | 6 | It using filesystem ot store media files in, and database to store existing media to object bindings. Following media types 7 | special behaviour supposed: 8 | ConstMediaTypeImage ("image") - files stored in filesystem as set of specified image sizes 9 | ConstMediaTypeLink ("link") - external resource - not stored in filesystem 10 | ConstMediaTypeDocument ("document"), and others - file stored in filesystem but have not and image 11 | 12 | Files are stored within filesystem using following pattern: [ConstMediaDefaultFolder]/[mediaType]/[objectModelName]/[obejctID]/[mediaFileName]. 13 | 14 | "image" type media re-sizes to [ConstConfigPathMediaImageSize] and set of [ConstConfigPathMediaImageSizes] sizes - these 15 | sizes are specified by config values (stored supposedly in DB). On program first startup them are initialized to 16 | [ConstDefaultImageSize] and [ConstDefaultImageSizes] (the same behaviour for config invalid values). Image resizing happens 17 | with usage of white background if [ConstResizeOnBackground] is set to true. 18 | 19 | Database record contain only "base image" record other sizes are named wit a following pattern: 20 | baseImage: [fileName].[fileExtension] 21 | sizedImage: [fileName]_[sizeName].[fileExtension] 22 | 23 | Image sizes is a config value like "small: 75x75, thumb: 260x300, big: 560x650", where key means [sizeName] and value is 24 | size [maxWidth]x[maxHeight] definition. If [sizeName] is not specified iw will equal to value (i.e. "75x75, thumb: 50x50" 25 | equals to "75x75: 75x75, thumb: 50x50"). Image re-sizes to [maxWidth]x[maxHeight] bounding box, "0" dimension have a 26 | special meaning - it means that in this direction image can be any size ("100x0" means that image height is not limited) 27 | Image re-sizing happens with keeping of image aspect ratio. 28 | */ 29 | package fsmedia 30 | -------------------------------------------------------------------------------- /media/interfaces.go: -------------------------------------------------------------------------------- 1 | package media 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | ) 6 | 7 | // Package global constants 8 | const ( 9 | ConstErrorModule = "media" 10 | ConstErrorLevel = env.ConstErrorLevelService 11 | 12 | ConstMediaTypeImage = "image" 13 | ConstMediaTypeLink = "link" 14 | ConstMediaTypeDocument = "document" 15 | ) 16 | 17 | // InterfaceMediaStorage is an interface to access media storage service 18 | type InterfaceMediaStorage interface { 19 | GetName() string 20 | 21 | Load(model string, objID string, mediaType string, mediaName string) ([]byte, error) 22 | Save(model string, objID string, mediaType string, mediaName string, mediaData []byte) error 23 | 24 | Remove(model string, objID string, mediaType string, mediaName string) error 25 | 26 | ListMedia(model string, objID string, mediaType string) ([]string, error) 27 | ListMediaDetail(model string, objID string, mediaType string) ([]map[string]interface{}, error) 28 | 29 | GetMediaPath(model string, objID string, mediaType string) (string, error) 30 | GetMediaByPath(mediaPath string) ([]byte, error) 31 | 32 | GetAllSizes(model string, objID string, mediaType string) ([]map[string]string, error) 33 | GetSizes(model string, objID string, mediaType string, mediaName string) (map[string]string, error) 34 | 35 | ResizeAllMediaImages() error 36 | } 37 | -------------------------------------------------------------------------------- /media/manager.go: -------------------------------------------------------------------------------- 1 | package media 2 | 3 | import ( 4 | "github.com/ottemo/commerce/env" 5 | ) 6 | 7 | // Package global variables 8 | var ( 9 | currentMediaStorage InterfaceMediaStorage // currently registered media storage service in system 10 | callbacksOnMediaStorageStart = []func() error{} // set of callback function on media storage service start 11 | ) 12 | 13 | // RegisterOnMediaStorageStart registers new callback on media storage service start 14 | func RegisterOnMediaStorageStart(callback func() error) { 15 | callbacksOnMediaStorageStart = append(callbacksOnMediaStorageStart, callback) 16 | } 17 | 18 | // OnMediaStorageStart fires media storage service start event (callback handling) 19 | func OnMediaStorageStart() error { 20 | for _, callback := range callbacksOnMediaStorageStart { 21 | if err := callback(); err != nil { 22 | return err 23 | } 24 | } 25 | return nil 26 | } 27 | 28 | // RegisterMediaStorage registers media storage service in the system 29 | // - will cause error if there are couple candidates for that role 30 | func RegisterMediaStorage(newEngine InterfaceMediaStorage) error { 31 | if currentMediaStorage == nil { 32 | currentMediaStorage = newEngine 33 | } else { 34 | return env.ErrorNew(ConstErrorModule, ConstErrorLevel, "eaf00795-12f6-4ea9-8ce2-7cc89088af14", "Sorry, '"+currentMediaStorage.GetName()+"' media storage already registered") 35 | } 36 | return nil 37 | } 38 | 39 | // GetMediaStorage returns currently used media storage service implementation 40 | func GetMediaStorage() (InterfaceMediaStorage, error) { 41 | if currentMediaStorage != nil { 42 | return currentMediaStorage, nil 43 | } 44 | return nil, env.ErrorNew(ConstErrorModule, ConstErrorLevel, "2bac26ac-67c6-4db4-9d07-1b813288a1af", "no registered media storage") 45 | } 46 | -------------------------------------------------------------------------------- /ottemo.sample.ini: -------------------------------------------------------------------------------- 1 | ; Sample Configuration 2 | 3 | ; DB Connections 4 | ; MySQL Settings: [username]:[password]@[protocol[([address])]]/[dbname] 5 | db.mysql.db=ottemo 6 | db.mysql.uri=root@/ 7 | db.mysql.maxConnections=50 8 | db.mysql.poolConnections=10 9 | 10 | ; Postgres Settings: postgres://[username]:[password]@[host]/[initial_schema]?[option=value,...] 11 | ; db.postgres.db=ottemo 12 | ; db.postgres.uri=postgres://localhost/postgres?sslmode=disable 13 | ; db.postgres.maxConnections=50 14 | ; db.postgres.poolConnections=10 15 | 16 | ; MongoDB Settings: [username]:[password]@[address:port]]/[dbname] 17 | ; mongodb.db=ottemo-dev 18 | ; mongodb.uri=mongodb://ottemo:ottemo@candidate.42.mongolayer.com:10243/ottemo-dev 19 | 20 | ; Other Settings 21 | media.fsmedia.folder=./media/ 22 | media.resize.images.onfly=false 23 | 24 | secure_cookie=false 25 | xdomain.master=http://*.ottemo.io/ 26 | -------------------------------------------------------------------------------- /test/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Ottemo. All rights reserved. 2 | 3 | /* 4 | Package test represents set of test writing helpers and global application tests. It provides a set of functions you can 5 | use for starting Ottemo application in a test mode, prepare randomized data, fill DB with sample data, which is use-full 6 | during GO tests writing. 7 | 8 | Package also contains a set of benchmarks and tests which related to whole application rather when particular package. 9 | In order to run them use: 10 | go test [-tags ...] github.com/ottemo/commerce/tests 11 | go test -bench . [-tags ...] github.com/ottemo/commerce/tests 12 | 13 | (refer to http://golang.org/pkg/testing/ for details) 14 | */ 15 | package test 16 | -------------------------------------------------------------------------------- /utils/json.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | ) 7 | 8 | // EncodeToJSONString encodes inputData to JSON string if it's possible 9 | func EncodeToJSONString(inputData interface{}) string { 10 | result, _ := json.Marshal(inputData) 11 | return string(result) 12 | } 13 | 14 | // DecodeJSONToArray decodes json string to []interface{} if it's possible 15 | func DecodeJSONToArray(jsonData interface{}) ([]interface{}, error) { 16 | var result []interface{} 17 | 18 | var err error 19 | switch value := jsonData.(type) { 20 | case string: 21 | err = json.Unmarshal([]byte(value), &result) 22 | case []byte: 23 | err = json.Unmarshal(value, &result) 24 | default: 25 | err = errors.New("Unsupported json type for conversion to array") 26 | } 27 | 28 | return result, err 29 | } 30 | 31 | // DecodeJSONToStringKeyMap decodes json string to map[string]interface{} if it's possible 32 | func DecodeJSONToStringKeyMap(jsonData interface{}) (map[string]interface{}, error) { 33 | 34 | result := make(map[string]interface{}) 35 | 36 | var err error 37 | 38 | switch value := jsonData.(type) { 39 | case string: 40 | err = json.Unmarshal([]byte(value), &result) 41 | case []byte: 42 | err = json.Unmarshal(value, &result) 43 | default: 44 | err = errors.New("Unable to create map, unsupported json type") 45 | } 46 | 47 | return result, err 48 | } 49 | 50 | // DecodeJSONToInterface decodes json string to interface{} if it's possible 51 | func DecodeJSONToInterface(jsonData interface{}) (interface{}, error) { 52 | 53 | var result interface{} 54 | 55 | var err error 56 | 57 | switch value := jsonData.(type) { 58 | case string: 59 | err = json.Unmarshal([]byte(value), &result) 60 | case []byte: 61 | err = json.Unmarshal(value, &result) 62 | default: 63 | err = errors.New("Unable to parse json, unsupported json type") 64 | } 65 | 66 | return result, err 67 | } 68 | -------------------------------------------------------------------------------- /utils/rounding_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestRoundPrice(t *testing.T) { 8 | if x := RoundPrice(32.87000000001); x != 32.87 { 9 | t.Error("incorect result:", x) 10 | } 11 | if x := RoundPrice(-32.87000000001); x != -32.87 { 12 | t.Error("incorect result:", x) 13 | } 14 | 15 | if x := RoundPrice(32.0); x != 32.0 { 16 | t.Error("incorect result:", x) 17 | } 18 | if x := RoundPrice(-32.0); x != -32.0 { 19 | t.Error("incorect result:", x) 20 | } 21 | 22 | if x := RoundPrice(32.865); x != 32.87 { 23 | t.Error("incorect result:", x) 24 | } 25 | if x := RoundPrice(-32.865); x != -32.87 { 26 | t.Error("incorect result:", x) 27 | } 28 | 29 | if x := RoundPrice(32.8699999999995); x != 32.87 { 30 | t.Error("incorect result:", x) 31 | } 32 | if x := RoundPrice(-32.8699999999995); x != -32.87 { 33 | t.Error("incorect result:", x) 34 | } 35 | 36 | if x := RoundPrice(0.0045); x != 0 { 37 | t.Error("incorect result:", x) 38 | } 39 | if x := RoundPrice(-0.0045); x != 0 { 40 | t.Error("incorect result:", x) 41 | } 42 | 43 | if x := RoundPrice(0.005); x != 0.01 { 44 | t.Error("incorect result:", x) 45 | } 46 | if x := RoundPrice(-0.005); x != -0.01 { 47 | t.Error("incorect result:", x) 48 | } 49 | 50 | if x := 1 - RoundPrice(0.000000000000009); x != 1 { 51 | t.Error("incorect result:", x) 52 | } 53 | if x := RoundPrice(0.000000000000009) - 1; x != -1 { 54 | t.Error("incorect result:", x) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /utils/templates.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "text/template" 6 | ) 7 | 8 | var ( 9 | templateFuncs = make(map[string]interface{}) 10 | ) 11 | 12 | // RegisterTemplateFunction registers custom function within text template processing 13 | func RegisterTemplateFunction(name string, function interface{}) error { 14 | templateFuncs[name] = function 15 | return nil 16 | } 17 | 18 | // GetTemplateFunctions returns clone of templateFuncs (safe to manipulate) 19 | func GetTemplateFunctions() map[string]interface{} { 20 | result := make(map[string]interface{}) 21 | for key, value := range templateFuncs { 22 | result[key] = value 23 | } 24 | return result 25 | } 26 | 27 | // TextTemplate evaluates text template, returns error if not possible 28 | func TextTemplate(templateContents string, context map[string]interface{}) (string, error) { 29 | 30 | textTemplate, err := template.New("TextTemplate").Funcs(templateFuncs).Parse(templateContents) 31 | if err != nil { 32 | return "", err 33 | } 34 | 35 | var result bytes.Buffer 36 | err = textTemplate.Execute(&result, context) 37 | if err != nil { 38 | return "", err 39 | } 40 | 41 | return result.String(), nil 42 | } 43 | --------------------------------------------------------------------------------