From 5889499a7e8529fe631257e025307d02b1244e44 Mon Sep 17 00:00:00 2001 From: "pakintada@gmail.com" Date: Mon, 5 Feb 2024 12:22:24 +0700 Subject: [PATCH 1/7] merge doing topping and re-save file --- .../features/toppings/toppings.component.html | 87 +++++++++++++++---- .../features/toppings/toppings.component.ts | 47 +++++++--- server/go.mod | 6 ++ server/go.sum | 6 ++ server/server.go | 5 +- 5 files changed, 119 insertions(+), 32 deletions(-) diff --git a/client/src/app/features/toppings/toppings.component.html b/client/src/app/features/toppings/toppings.component.html index 2260617..2870e82 100644 --- a/client/src/app/features/toppings/toppings.component.html +++ b/client/src/app/features/toppings/toppings.component.html @@ -1,6 +1,15 @@ -
+
+
+ + +
+ - + @@ -12,28 +21,72 @@ - - - - - - - + + + + + + + -
Is Use ID
+ + + + + + + + + + -
+
+
+
-
+ + + + diff --git a/client/src/app/features/toppings/toppings.component.ts b/client/src/app/features/toppings/toppings.component.ts index 51c1f4b..0407c96 100644 --- a/client/src/app/features/toppings/toppings.component.ts +++ b/client/src/app/features/toppings/toppings.component.ts @@ -16,7 +16,7 @@ import { NgSelectModule } from '@ng-select/ng-select'; @Component({ selector: 'app-toppings', standalone: true, - imports: [CommonModule, NgSelectModule,FormsModule, ReactiveFormsModule], + imports: [CommonModule, NgSelectModule, FormsModule, ReactiveFormsModule], templateUrl: './toppings.component.html', }) export class ToppingsComponent implements OnInit { @@ -24,10 +24,12 @@ export class ToppingsComponent implements OnInit { toppingGroupList: ToppingGroup[] = []; toppingLists: ToppingList[] = []; - groupMemebersMap: { [key: string]: { [key: string]: any } } = { + groupMembersMap: { [key: string]: { [key: string]: any } } = { '0': { members: [] }, }; + showToppingBuilder: boolean = false; + // forms toppingGroupForm = this._formBuilder.group( @@ -67,7 +69,7 @@ export class ToppingsComponent implements OnInit { 'topping groups: ', this.toppingGroupList, 'mapper:', - this.groupMemebersMap, + this.groupMembersMap, 'length', this.toppingGroupList.length ); @@ -103,7 +105,7 @@ export class ToppingsComponent implements OnInit { 'get topping list', this.toppingLists, 'mapper:', - this.groupMemebersMap + this.groupMembersMap ); console.log('undefined name of topping list', this.findUndefinedName()); @@ -116,26 +118,26 @@ export class ToppingsComponent implements OnInit { mapMembers = () => { this.toppingGroupList.forEach((tpg) => { let spl_mem = tpg.idInGroup.split(','); - this.groupMemebersMap[tpg.groupID] = { members: spl_mem }; + this.groupMembersMap[tpg.groupID] = { members: spl_mem }; }); }; // get members of group id getMemberByGroupId = (id: string) => - this.groupMemebersMap[id]['members'] as string[]; + this.groupMembersMap[id]['members'] as string[]; // match name to member mapNameToMember = () => { if (this.toppingLists.length > 0) { this.toppingGroupList.forEach((tpg) => { - let members = this.groupMemebersMap[tpg.groupID]['members']; + let members = this.groupMembersMap[tpg.groupID]['members']; members.forEach((member_id: string) => { // do get member data from topping list let member_data = this.toppingLists.find( (v) => v.id.toString() == member_id.toString() ); // set data to group - this.groupMemebersMap[tpg.groupID][member_id] = member_data; + this.groupMembersMap[tpg.groupID][member_id] = member_data; }); }); } @@ -143,10 +145,10 @@ export class ToppingsComponent implements OnInit { // get member data from group getMemberData = (group: string, member_id: string) => { - if (this.groupMemebersMap[group][member_id] == undefined) { - return {}; - } - return this.groupMemebersMap[group][member_id]; + // if (this.groupMembersMap[group][member_id] == undefined) { + // return {}; + // } + return this.groupMembersMap[group][member_id]; }; // check which list does not have name @@ -158,11 +160,28 @@ export class ToppingsComponent implements OnInit { value == undefined || value == '' ? default_value : value; // get value from form by given key - getAttrFromForm(index: number, key: string){ + getAttrFromForm(index: number, key: string) { let x = this.toppingGroup.controls.at(index) as any; return x.value[key]; } // diff and find matched - comapareFunction = (a: any, b: any) => ( a != undefined && b != undefined)&& (a.toString() === b.toString()); + comapareFunction = (a: any, b: any) => + a != undefined && b != undefined && a.toString() === b.toString(); + + // onclick: set default of topping in the group + setDefaultOfToppingGroup = (index: number, member: string) => { + // get default of group in form + let targetForm = this.toppingGroup.controls.at(index); + console.log('get id default', targetForm); + + let targetDefault = targetForm?.get('idDefault'); + + // if click on the same index, set 0 + if (targetDefault?.value == member) { + member = '0'; + } + + targetDefault?.setValue(member); + }; } diff --git a/server/go.mod b/server/go.mod index c09e67a..7d5c6d9 100644 --- a/server/go.mod +++ b/server/go.mod @@ -18,6 +18,12 @@ require ( golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect ) +require ( + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/redis/go-redis/v9 v9.4.0 // indirect +) + require ( cloud.google.com/go/compute v1.23.3 // indirect cloud.google.com/go/compute/metadata v0.2.3 // indirect diff --git a/server/go.sum b/server/go.sum index 721a99a..9c25cb5 100644 --- a/server/go.sum +++ b/server/go.sum @@ -48,6 +48,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40 h1:y4B3+GPxKlrigF1ha5FFErxK+sr6sWxQovRMzwMhejo= github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -58,6 +60,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -188,6 +192,8 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk= +github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= diff --git a/server/server.go b/server/server.go index 16a08c4..5b2853b 100644 --- a/server/server.go +++ b/server/server.go @@ -20,6 +20,7 @@ import ( "strings" "github.com/jmoiron/sqlx" + "github.com/redis/go-redis/v9" "github.com/go-chi/chi/v5" "github.com/go-chi/cors" @@ -30,6 +31,7 @@ type Server struct { server *http.Server data *data.Data database *sqlx.DB + cache_db *redis.Client cfg *config.ServerConfig oauth oauth.OAuthService taoLogger *logger.TaoLogger @@ -44,8 +46,9 @@ func NewServer(cfg *config.ServerConfig, oauthService oauth.OAuthService) *Serve server: &http.Server{Addr: fmt.Sprintf(":%d", cfg.ServerPort)}, data: data.NewData(taoLogger), database: data.NewSqliteDatabase(), + cache_db: data.NewRedisClient("redis:6379", "", ""), cfg: cfg, - oauth: oauthService, + oauth: oauth.NewOAuthService(serverCfg), taoLogger: taoLogger, } } From 95a9bebb4a67eb12dbc23e360fe31670e0898fe9 Mon Sep 17 00:00:00 2001 From: "pakintada@gmail.com" Date: Mon, 5 Feb 2024 12:26:30 +0700 Subject: [PATCH 2/7] ignore this --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 068f6e5..5606b39 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ server/services/logger/serverlog.log .idea/ .DS_Store server/cofffeemachineConfig +node_modules +server/cofffeemachineConfig.diff From 0604a3b77f6c96fe6ad59e0cebc5ae9617ef88ff Mon Sep 17 00:00:00 2001 From: "pakintada@gmail.com" Date: Mon, 5 Feb 2024 17:07:04 +0700 Subject: [PATCH 3/7] Add redis. Prep for new save file --- server/data/data.go | 215 +++++++++++++++++++++++-------------- server/data/redis.go | 137 +++++++++++++++++++++++ server/routers/material.go | 6 ++ server/routers/recipe.go | 5 +- server/server.go | 19 ++-- 5 files changed, 295 insertions(+), 87 deletions(-) create mode 100644 server/data/redis.go diff --git a/server/data/data.go b/server/data/data.go index 102ae8a..2091222 100644 --- a/server/data/data.go +++ b/server/data/data.go @@ -27,10 +27,11 @@ type Data struct { CurrentCountryID map[string]string DefaultCountryMap []DefaultByCountry AllRecipeFiles map[string][]helpers.RecipePath - currentRecipe map[string]*models.Recipe + CurrentRecipe map[string]*models.Recipe recipeMap map[string]RecipeWithTimeStamps Countries []helpers.CountryName taoLogger *logger.TaoLogger + redisClient *RedisCli } type DefaultByCountry struct { @@ -53,7 +54,7 @@ var ( } ) -func NewData(taoLogger *logger.TaoLogger) *Data { +func NewData(taoLogger *logger.TaoLogger, redisClient *RedisCli) *Data { allRecipeFiles := helpers.ScanRecipeFiles(countries) @@ -131,6 +132,8 @@ func NewData(taoLogger *logger.TaoLogger) *Data { log.Panic("Error when read default recipe file for each country:", v.CountryShortName, err) } + redisClient.SetToKey(v2.Name, defaultRecipe) + currentDefaultFileForEachCountry[v.CountryShortName] = defaultRecipe break } @@ -169,7 +172,7 @@ func NewData(taoLogger *logger.TaoLogger) *Data { CurrentFile: currentFileMap, CurrentCountryID: CurrentCountryIDMap, AllRecipeFiles: allRecipeFiles, - currentRecipe: currentDefaultFileForEachCountry, + CurrentRecipe: currentDefaultFileForEachCountry, recipeMap: map[string]RecipeWithTimeStamps{ defaultFile: { Recipe: currentDefaultFileForEachCountry, @@ -179,6 +182,7 @@ func NewData(taoLogger *logger.TaoLogger) *Data { Countries: countries, taoLogger: taoLogger, DefaultCountryMap: defaultForEachCountry, + redisClient: redisClient, } } @@ -189,17 +193,47 @@ func (d *Data) GetRecipe(countryID, filename string) *models.Recipe { // TODO: concat submenu into recipe if countryID == "" { - return d.currentRecipe["tha"] + d.taoLogger.Log.Debug("GetRecipe", zap.Any("EmptyCountryId", "return default country = tha")) + return d.CurrentRecipe["tha"] } - if filename == "" || filename == d.CurrentFile[countryID] { - return d.currentRecipe[countryID] + if filename == "" { + d.taoLogger.Log.Debug("GetRecipe", zap.Any("EmptyFilename", filename)) + return d.CurrentRecipe[countryID] } - if recipe, ok := d.recipeMap[filename]; ok { + // do check if match the current pointer + if d.CurrentFile[countryID] == filename { + d.taoLogger.Log.Debug("GetRecipe", zap.Any("FileMatchCurrent", "return from stored "+filename), zap.Any("CurrentFile", d.CurrentFile)) + + // make sure recipe vesion is equal + currentConfig := d.CurrentRecipe[countryID].MachineSetting.ConfigNumber + // get requested version + requestedConfig, _ := strconv.Atoi(strings.Split(strings.Split(filename, "_")[1], ".")[0]) + if currentConfig != requestedConfig { + d.taoLogger.Log.Debug("GetRecipe", zap.Any("ActualFileNotMatch", "Skip this!")) + } else { + // if equal, OK + return d.CurrentRecipe[countryID] + } + + } + + if recipe, ok := d.recipeMap[filename]; ok && d.redisClient.HealthCheck() != nil { + d.taoLogger.Log.Debug("GetRecipe", zap.Any("ValidOnStored", "return from stored "+filename)) d.CurrentFile[countryID] = filename // d.CurrentCountryID[countryID] = countryID - return recipe.Recipe[countryID] + + // make sure recipe vesion is equal + currentConfig := d.CurrentRecipe[countryID].MachineSetting.ConfigNumber + // get requested version + requestedConfig, _ := strconv.Atoi(strings.Split(strings.Split(filename, "_")[1], ".")[0]) + if currentConfig != requestedConfig { + d.taoLogger.Log.Debug("GetRecipe", zap.Any("InvalidOnStored", "Skip this!")) + } else { + // if equal, OK + return recipe.Recipe[countryID] + } } // change current version and read new recipe @@ -208,17 +242,49 @@ func (d *Data) GetRecipe(countryID, filename string) *models.Recipe { filename = d.CurrentFile[countryID] } - // d.CurrentFile[countryID] = filename - d.taoLogger.Log.Debug("GetRecipe", zap.String("filename", filename), zap.String("countryID", countryID)) - // d.CurrentCountryID[countryID] = countryID + // var recipe *models.Recipe = &models.Recipe{} + + // do check if redis contains the recipe + var cached_recipe *models.Recipe = &models.Recipe{} + + if err := d.redisClient.GetKeyTo(filename, cached_recipe); err != nil { + d.taoLogger.Log.Debug("GetRecipe.Cached", zap.Any("GetCacheRecipeError", err)) + d.taoLogger.Log.Debug("GetRecipe", zap.String("filename", filename), zap.String("countryID", countryID)) + // d.CurrentCountryID[countryID] = countryID + cached_recipe = nil + } + + if cached_recipe != nil { + d.taoLogger.Log.Debug("GetRecipe", zap.Any("Check on cached recipe invalid", cached_recipe == nil), zap.Any("test config number", cached_recipe.MachineSetting.ConfigNumber)) + } + recipe, err := helpers.ReadRecipeFile(countryID, filename) if err != nil { - d.taoLogger.Log.Error("GetRecipe: Error when read recipe file, Return default recipe", zap.Error(err)) - return d.currentRecipe[countryID] + d.taoLogger.Log.Debug("GetRecipe", zap.Any("ReadRecipeError -> return default", err)) + return d.CurrentRecipe[countryID] } - d.currentRecipe[countryID] = recipe + // cache to redis + d.redisClient.SetToKey(filename, recipe) + + if err != nil { + d.taoLogger.Log.Error("GetRecipe: Error when read recipe file, Return default recipe", zap.Error(err)) + return d.CurrentRecipe[countryID] + } + + //. service is connected. Use from cache + + // check healthcheck redis + err = d.redisClient.HealthCheck() + d.taoLogger.Log.Info("GetRecipe: HealthCheck", zap.Any("result", err)) + if d.redisClient.HealthCheck() == nil && cached_recipe != nil { + d.taoLogger.Log.Debug("GetRecipeCached", zap.Any("cached_recipe", "yes")) + d.CurrentRecipe[countryID] = cached_recipe + } else { + d.taoLogger.Log.Debug("GetRecipeCached", zap.Any("cached_recipe", "no")) + d.CurrentRecipe[countryID] = recipe + } // save to map if len(d.recipeMap) > 5 { // limit keep in memory 5 version @@ -235,11 +301,11 @@ func (d *Data) GetRecipe(countryID, filename string) *models.Recipe { } d.recipeMap[filename] = RecipeWithTimeStamps{ - Recipe: d.currentRecipe, + Recipe: d.CurrentRecipe, TimeStamps: time.Now().Unix(), } - return d.currentRecipe[countryID] + return d.CurrentRecipe[countryID] } // func (d *Data) GetRecipe01() []models.Recipe01 { @@ -272,7 +338,7 @@ func (d *Data) GetRecipe01ByProductCode(filename, countryID, productCode string) // fmt.Println("GetRecipe01ByProductCode.ReadCurrent::CurrentFile", d.CurrentFile) // fmt.Println("GetRecipe01ByProductCode.ReadCurrent::CurrentCountryID", d.CurrentCountryID) - for _, v := range d.currentRecipe[countryID].Recipe01 { + for _, v := range d.CurrentRecipe[countryID].Recipe01 { if v.ProductCode == productCode { return v, nil } else if len(v.SubMenu) > 0 { @@ -320,26 +386,26 @@ func (d *Data) GetRecipe01ByProductCode(filename, countryID, productCode string) } } - recipe, err := helpers.ReadRecipeFile(countryID, filename) + recipe := d.GetRecipe(countryID, filename) - if err != nil { - d.taoLogger.Log.Error("GetRecipe01ByProductCode: Error when read recipe file, Return default recipe", zap.Error(err)) - for _, v := range d.currentRecipe[countryID].Recipe01 { - if v.ProductCode == productCode { - return v, fmt.Errorf("[DEFAULT]-ERR") - } else if len(v.SubMenu) > 0 { - for _, subMenu := range v.SubMenu { - if subMenu.ProductCode == productCode { - return subMenu, fmt.Errorf("[DEFAULT]-ERR") - } - } - } - } - } + // if err != nil { + // d.taoLogger.Log.Error("GetRecipe01ByProductCode: Error when read recipe file, Return default recipe", zap.Error(err)) + // for _, v := range d.CurrentRecipe[countryID].Recipe01 { + // if v.ProductCode == productCode { + // return v, fmt.Errorf("[DEFAULT]-ERR") + // } else if len(v.SubMenu) > 0 { + // for _, subMenu := range v.SubMenu { + // if subMenu.ProductCode == productCode { + // return subMenu, fmt.Errorf("[DEFAULT]-ERR") + // } + // } + // } + // } + // } d.taoLogger.Log.Debug("GetRecipe01ByProductCode", zap.Any("productCode", productCode), zap.Any("version", recipe.MachineSetting.ConfigNumber)) - d.currentRecipe[countryID] = recipe + d.CurrentRecipe[countryID] = recipe // save to map if len(d.recipeMap) > 5 { // limit keep in memory 5 version @@ -356,11 +422,11 @@ func (d *Data) GetRecipe01ByProductCode(filename, countryID, productCode string) } d.recipeMap[filename] = RecipeWithTimeStamps{ - Recipe: d.currentRecipe, + Recipe: d.CurrentRecipe, TimeStamps: time.Now().Unix(), } - for _, v := range d.currentRecipe[countryID].Recipe01 { + for _, v := range d.CurrentRecipe[countryID].Recipe01 { if v.ProductCode == productCode { // d.taoLogger.Log.Debug("GetRecipe01ByProductCode", zap.Any("productCode", productCode), zap.Any("result", v)) return v, nil @@ -438,21 +504,21 @@ func (d *Data) GetMaterialSetting(countryID, filename string) []models.MaterialS if countryID == "" { // copy(result, d.currentRecipe[countryID].MaterialSetting) - return d.currentRecipe[countryID].MaterialSetting + return d.CurrentRecipe[countryID].MaterialSetting } if !strings.Contains(filename, "tmp") { if filename == "" || filename == d.CurrentFile[countryID] { // copy(result, d.currentRecipe[countryID].MaterialSetting) // d.taoLogger.Log.Debug("GetMaterialSetting", zap.Any("result", result)) - return d.currentRecipe[countryID].MaterialSetting + return d.CurrentRecipe[countryID].MaterialSetting } if recipe, ok := d.recipeMap[filename]; ok { copy(result, recipe.Recipe[countryID].MaterialSetting) d.CurrentFile[countryID] = filename // d.CurrentCountryID[countryID] = countryID - return d.currentRecipe[countryID].MaterialSetting + return d.CurrentRecipe[countryID].MaterialSetting } } @@ -464,17 +530,17 @@ func (d *Data) GetMaterialSetting(countryID, filename string) []models.MaterialS // d.CurrentFile[countryID] = filename // d.CurrentCountryID[countryID] = countryID - recipe, err := helpers.ReadRecipeFile(countryID, filename) + recipe := d.GetRecipe(countryID, filename) - if err != nil { - d.taoLogger.Log.Error("GetMaterialSetting: Error when read recipe file, Return default recipe", zap.Error(err)) - copy(result, d.currentRecipe[countryID].MaterialSetting) - return d.currentRecipe[countryID].MaterialSetting - } + // if err != nil { + // d.taoLogger.Log.Error("GetMaterialSetting: Error when read recipe file, Return default recipe", zap.Error(err)) + // copy(result, d.CurrentRecipe[countryID].MaterialSetting) + // return d.CurrentRecipe[countryID].MaterialSetting + // } // d.taoLogger.Log.Debug("GetMaterialSetting", zap.Any("recipe", recipe.MaterialSetting)) - d.currentRecipe[countryID] = recipe + d.CurrentRecipe[countryID] = recipe // save to map if len(d.recipeMap) > 5 { // limit keep in memory 5 version @@ -491,7 +557,7 @@ func (d *Data) GetMaterialSetting(countryID, filename string) []models.MaterialS } d.recipeMap[filename] = RecipeWithTimeStamps{ - Recipe: d.currentRecipe, + Recipe: d.CurrentRecipe, TimeStamps: time.Now().Unix(), } @@ -501,29 +567,26 @@ func (d *Data) GetMaterialSetting(countryID, filename string) []models.MaterialS func (d *Data) GetAllToppingGroups(countryID, filename string) []models.ToppingGroup { if countryID == "" { - return d.currentRecipe[countryID].Topping.ToppingGroup + return d.CurrentRecipe[countryID].Topping.ToppingGroup } if !strings.Contains(filename, ".tmp") { if filename == "" || filename == d.CurrentFile[countryID] { - return d.currentRecipe[countryID].Topping.ToppingGroup + return d.CurrentRecipe[countryID].Topping.ToppingGroup } if _, ok := d.recipeMap[countryID]; ok { d.CurrentFile[countryID] = filename - return d.currentRecipe[countryID].Topping.ToppingGroup + return d.CurrentRecipe[countryID].Topping.ToppingGroup } } if filename == "default" { filename = d.CurrentFile[countryID] } - recipe, err := helpers.ReadRecipeFile(countryID, filename) - if err != nil { - return d.currentRecipe[countryID].Topping.ToppingGroup - } + recipe := d.GetRecipe(countryID, filename) - d.currentRecipe[countryID] = recipe + d.CurrentRecipe[countryID] = recipe if len(d.recipeMap) > 5 { // limit keep in memory 5 version // remove oldest version @@ -539,7 +602,7 @@ func (d *Data) GetAllToppingGroups(countryID, filename string) []models.ToppingG } d.recipeMap[filename] = RecipeWithTimeStamps{ - Recipe: d.currentRecipe, + Recipe: d.CurrentRecipe, TimeStamps: time.Now().Unix(), } @@ -549,17 +612,17 @@ func (d *Data) GetAllToppingGroups(countryID, filename string) []models.ToppingG func (d *Data) GetToppingsList(countryID, filename string) []models.ToppingList { // do return default if countryID == "" { - return d.currentRecipe[countryID].Topping.ToppingList + return d.CurrentRecipe[countryID].Topping.ToppingList } // handle temporary file if !strings.Contains(filename, ".tmp") { if filename == "" || filename == d.CurrentFile[countryID] { - return d.currentRecipe[countryID].Topping.ToppingList + return d.CurrentRecipe[countryID].Topping.ToppingList } if _, ok := d.recipeMap[countryID]; ok { d.CurrentFile[countryID] = filename - return d.currentRecipe[countryID].Topping.ToppingList + return d.CurrentRecipe[countryID].Topping.ToppingList } } @@ -567,10 +630,7 @@ func (d *Data) GetToppingsList(countryID, filename string) []models.ToppingList filename = d.CurrentFile[countryID] } - recipe, err := helpers.ReadRecipeFile(countryID, filename) - if err != nil { - return d.currentRecipe[countryID].Topping.ToppingList - } + recipe := d.GetRecipe(countryID, filename) if len(d.recipeMap) > 5 { // limit keep in memory 5 version // remove oldest version @@ -586,7 +646,7 @@ func (d *Data) GetToppingsList(countryID, filename string) []models.ToppingList } d.recipeMap[filename] = RecipeWithTimeStamps{ - Recipe: d.currentRecipe, + Recipe: d.CurrentRecipe, TimeStamps: time.Now().Unix(), } @@ -597,7 +657,7 @@ func (d *Data) GetMaterialCode(ids []uint64, countryID, filename string) []model var result []models.MaterialCode if filename == "" || filename == d.CurrentFile[countryID] { - result = d.currentRecipe[countryID].MaterialCode + result = d.CurrentRecipe[countryID].MaterialCode } else if recipe, ok := d.recipeMap[filename]; ok { d.CurrentFile[countryID] = filename return recipe.Recipe[countryID].MaterialCode @@ -609,14 +669,14 @@ func (d *Data) GetMaterialCode(ids []uint64, countryID, filename string) []model // d.CurrentFile[countryID] = filename // d.CurrentCountryID[countryID] = countryID - recipe, err := helpers.ReadRecipeFile(countryID, filename) + recipe := d.GetRecipe(countryID, filename) - if err != nil { - d.taoLogger.Log.Error("GetMaterialCode: Error when read recipe file, Return default recipe", zap.Error(err)) - return d.currentRecipe[countryID].MaterialCode - } + // if err != nil { + // d.taoLogger.Log.Error("GetMaterialCode: Error when read recipe file, Return default recipe", zap.Error(err)) + // return d.CurrentRecipe[countryID].MaterialCode + // } - d.currentRecipe[countryID] = recipe + d.CurrentRecipe[countryID] = recipe // save to map if len(d.recipeMap) > 5 { // limit keep in memory 5 version @@ -633,11 +693,11 @@ func (d *Data) GetMaterialCode(ids []uint64, countryID, filename string) []model } d.recipeMap[filename] = RecipeWithTimeStamps{ - Recipe: d.currentRecipe, + Recipe: d.CurrentRecipe, TimeStamps: time.Now().Unix(), } - result = d.currentRecipe[countryID].MaterialCode + result = d.CurrentRecipe[countryID].MaterialCode } if len(ids) == 0 { @@ -664,7 +724,7 @@ func (d *Data) GetMaterialCode(ids []uint64, countryID, filename string) []model func (d *Data) GetToppings(countryID, filename string) models.Topping { if filename == "" || filename == d.CurrentFile[countryID] { - return d.currentRecipe[countryID].Topping + return d.CurrentRecipe[countryID].Topping } else if recipe, ok := d.recipeMap[filename]; ok { d.CurrentFile[countryID] = filename return recipe.Recipe[countryID].Topping @@ -676,14 +736,9 @@ func (d *Data) GetToppings(countryID, filename string) models.Topping { // d.CurrentFile[countryID] = filename // d.CurrentCountryID[countryID] = countryID - recipe, err := helpers.ReadRecipeFile(countryID, filename) + recipe := d.GetRecipe(countryID, filename) - if err != nil { - d.taoLogger.Log.Error("GetToppings: Error when read recipe file, Return default recipe", zap.Error(err)) - return d.currentRecipe[countryID].Topping - } - - d.currentRecipe[countryID] = recipe + d.CurrentRecipe[countryID] = recipe return recipe.Topping } diff --git a/server/data/redis.go b/server/data/redis.go new file mode 100644 index 0000000..865de1f --- /dev/null +++ b/server/data/redis.go @@ -0,0 +1,137 @@ +package data + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "time" + + "github.com/redis/go-redis/v9" +) + +type RedisCli struct { + Client *redis.Client +} + +func NewRedisClient(address, password string) *RedisCli { + + // option, err := redis.ParseURL("redis://" + username + ":" + password + "@localhost:6379/" + db) + + options := redis.Options{ + Addr: address, + Password: password, + DB: 0, + } + + client := redis.NewClient(&options) + + if err := client.Ping(context.Background()); err != nil { + fmt.Println("trying localhost ...", err) + client = redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: password, + DB: 0, + }) + + if err_local := client.Ping(context.Background()); err_local != nil { + fmt.Println("> result ====> ", err_local) + // do as warning + } else { + fmt.Println("\n> Localhost Redis OK!\n") + } + } else { + fmt.Println("\n> Redis OK! \n") + } + + return &RedisCli{ + Client: client, + } +} + +func (r *RedisCli) HealthCheck() error { + return r.Client.Ping(context.Background()).Err() +} + +func (r *RedisCli) GetKeyTo(source string, dest interface{}) error { + fmt.Println("Redis> GET ", source) + + // if cannot pass healthcheck, return err + if err := r.HealthCheck(); err != nil { + fmt.Println("HS> GET error ", err) + return err + } + + saved, err := r.Client.Get(context.Background(), source).Result() + + // chcek EOF + // fmt.Println("GET last char ", saved[len(saved)-1:]) + + if saved == "" || err != nil { + fmt.Println("GET error (empty on error)|", err, "|while saved=", saved) + return err + } + + // fmt.Println("GET ", saved) + + // if err != nil { + // fmt.Println("GET error ", err) + // return err + // } + err = json.NewDecoder(bytes.NewBufferString(saved)).Decode(dest) + + if err != nil { + fmt.Println("GET error ", err) + } + + return err +} + +func (r *RedisCli) SetToKey(key string, value interface{}) error { + fmt.Println("Redis> SET ", key) + + // if cannot pass healthcheck, return err + if err := r.HealthCheck(); err != nil { + fmt.Println("HS> SET error ", err) + return err + } + + saved, err := json.Marshal(value) + if err != nil { + fmt.Println("SET error ", err) + return err + } + + // late error + err = r.Client.Set(context.Background(), key, saved, redis.KeepTTL).Err() + + if err != nil { + fmt.Println("error on SET ", err) + } + + return err +} + +func (r *RedisCli) ExpireKey(key string) error { + + // if cannot pass healthcheck, return err + if err := r.HealthCheck(); err != nil { + fmt.Println("HS> EXPIRE error ", err) + return err + } + + return r.Client.Expire(context.Background(), key, 0).Err() +} + +func (r *RedisCli) SetKeyTimeout(key string, value interface{}, timeout int) error { + + // if cannot pass healthcheck, return err + if err := r.HealthCheck(); err != nil { + fmt.Println("HS> EXPIRE error ", err) + return err + } + + err := r.Client.Expire(context.Background(), key, time.Duration(timeout)*time.Second).Err() + fmt.Println("error on EXPIRE ", err) + return err +} diff --git a/server/routers/material.go b/server/routers/material.go index f8cf3af..87b8ec0 100644 --- a/server/routers/material.go +++ b/server/routers/material.go @@ -48,6 +48,12 @@ func (mr *MaterialRouter) GetFullMaterialDetail(w http.ResponseWriter, r *http.R matSettings := mr.data.GetMaterialSetting(country, filename) matCodes := mr.data.GetRecipe(country, filename).MaterialCode + if len(matSettings) == 0 { + // mr.taoLogger.Log.Error("MaterialRouter.GetFullMaterialDetail", zap.Error(err)) + http.Error(w, "Material not found", http.StatusNotFound) + return + } + // combine materialDetails := []map[string]interface{}{} diff --git a/server/routers/recipe.go b/server/routers/recipe.go index 0903aca..186fe32 100644 --- a/server/routers/recipe.go +++ b/server/routers/recipe.go @@ -30,6 +30,7 @@ type RecipeRouter struct { sheetService sheet.SheetService recipeService recipe.RecipeService taoLogger *logger.TaoLogger + cache_db *data.RedisCli } var ( @@ -37,12 +38,13 @@ var ( updateMutex = sync.Mutex{} ) -func NewRecipeRouter(data *data.Data, recipeService recipe.RecipeService, sheetService sheet.SheetService, taoLogger *logger.TaoLogger) *RecipeRouter { +func NewRecipeRouter(data *data.Data, recipeService recipe.RecipeService, sheetService sheet.SheetService, taoLogger *logger.TaoLogger, cache *data.RedisCli) *RecipeRouter { return &RecipeRouter{ data, sheetService, recipeService, taoLogger, + cache, } } @@ -460,6 +462,7 @@ func (rr *RecipeRouter) updateRecipe(w http.ResponseWriter, r *http.Request) { // gen hash commit_hash, err := data.HashCommit(8) + rr.cache_db.SetToKey(commit_hash, targetRecipe) commit := data.CommitLog{ diff --git a/server/server.go b/server/server.go index 5b2853b..95de08e 100644 --- a/server/server.go +++ b/server/server.go @@ -20,7 +20,6 @@ import ( "strings" "github.com/jmoiron/sqlx" - "github.com/redis/go-redis/v9" "github.com/go-chi/chi/v5" "github.com/go-chi/cors" @@ -31,7 +30,7 @@ type Server struct { server *http.Server data *data.Data database *sqlx.DB - cache_db *redis.Client + cache_db *data.RedisCli cfg *config.ServerConfig oauth oauth.OAuthService taoLogger *logger.TaoLogger @@ -42,13 +41,15 @@ func NewServer(cfg *config.ServerConfig, oauthService oauth.OAuthService) *Serve taoLogger := logger.NewTaoLogger(cfg) taoLogger.Log = taoLogger.Log.Named("Server") + redisClient := data.NewRedisClient("redis:6379", "") + return &Server{ server: &http.Server{Addr: fmt.Sprintf(":%d", cfg.ServerPort)}, - data: data.NewData(taoLogger), + data: data.NewData(taoLogger, redisClient), database: data.NewSqliteDatabase(), - cache_db: data.NewRedisClient("redis:6379", "", ""), + cache_db: redisClient, cfg: cfg, - oauth: oauth.NewOAuthService(serverCfg), + oauth: oauth.NewOAuthService(cfg), taoLogger: taoLogger, } } @@ -101,6 +102,12 @@ func (s *Server) createHandler() { ar.Route(r) }) + // Initial redis + for k, v := range s.data.CurrentRecipe { + s.taoLogger.Log.Debug("Caching", zap.Any("Recipe", k)) + s.cache_db.SetToKey(k, v) + } + // Protected Group r.Group(func(r chi.Router) { @@ -116,7 +123,7 @@ func (s *Server) createHandler() { } // Recipe Router - rr := routers.NewRecipeRouter(s.data, recipeService, sheetService, s.taoLogger) + rr := routers.NewRecipeRouter(s.data, recipeService, sheetService, s.taoLogger, s.cache_db) rr.Route(r) // Material Router From 18ea6402821687c168733c4965136b45545cf69f Mon Sep 17 00:00:00 2001 From: "pakintada@gmail.com" Date: Tue, 6 Feb 2024 16:07:10 +0700 Subject: [PATCH 4/7] addable & removable recipelist | savable topping & material WIP --- client/src/app/core/models/recipe.model.ts | 23 +- .../material-settings.component.html | 220 ++++++--- .../material-settings.component.ts | 113 ++++- .../recipe-details.component.html | 2 +- .../recipe-list/recipe-list.component.html | 13 +- .../recipe-list/recipe-list.component.ts | 424 ++++++++++-------- .../features/toppings/toppings.component.html | 124 ++++- .../features/toppings/toppings.component.ts | 50 ++- 8 files changed, 668 insertions(+), 301 deletions(-) diff --git a/client/src/app/core/models/recipe.model.ts b/client/src/app/core/models/recipe.model.ts index 02016b3..c3053a1 100644 --- a/client/src/app/core/models/recipe.model.ts +++ b/client/src/app/core/models/recipe.model.ts @@ -166,26 +166,31 @@ export interface ToppingSet { } export interface MaterialSetting { - StringParam: string, + materialName: string; + materialId: number; + materialOtherName: string; + RawMaterialUnit: string; + IceScreamBingsuChannel: boolean; + StringParam: string; AlarmIDWhenOffline: string; - BeanChannel: string; + BeanChannel: boolean; CanisterType: string; DrainTimer: string; - IsEquipment: string; - LeavesChannel: string; + IsEquipment: boolean; + LeavesChannel: boolean; LowToOffline: string; MaterialStatus: string; - PowderChannel: string; + PowderChannel: boolean; RefillUnitGram: string; RefillUnitMilliliters: string; RefillUnitPCS: string; ScheduleDrainType: string; SodaChannel: string; StockAdjust: string; - SyrupChannel: string; - id: string; - idAlternate: string; - isUse: string; + SyrupChannel: boolean; + id: number; + idAlternate: number; + isUse: boolean; pay_rettry_max_count: string; feed_mode: string; MaterialParameter: string; diff --git a/client/src/app/features/material-settings/material-settings.component.html b/client/src/app/features/material-settings/material-settings.component.html index d75d589..565b333 100644 --- a/client/src/app/features/material-settings/material-settings.component.html +++ b/client/src/app/features/material-settings/material-settings.component.html @@ -1,9 +1,20 @@ -
+
+

Material Settings

+ +
+ +
- {{ cat }} + + {{ cat }} +
+ + +
+
+ + +
@@ -22,66 +174,4 @@
- - - -
- - -
diff --git a/client/src/app/features/material-settings/material-settings.component.ts b/client/src/app/features/material-settings/material-settings.component.ts index f5bc4fe..36830c5 100644 --- a/client/src/app/features/material-settings/material-settings.component.ts +++ b/client/src/app/features/material-settings/material-settings.component.ts @@ -1,6 +1,7 @@ import { CommonModule, NgIf } from '@angular/common'; import { Component, OnInit } from '@angular/core'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { FormArray, FormBuilder, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MaterialSetting } from 'src/app/core/models/recipe.model'; import { MaterialService } from 'src/app/core/services/material.service'; import { getCategories, getMaterialType } from 'src/app/shared/helpers/recipe'; @@ -21,23 +22,36 @@ export class MaterialSettingsComponent implements OnInit { }[] | null = []; allMaterialsGroupedByTypes: { [key: string]: any[] } = {}; - currentMaterialSettings: any = null; + currentMaterialSettings: MaterialSetting | undefined = undefined; showMaterialSettingModal: boolean = false; - constructor(private _materialService: MaterialService) {} + // current form index + currentFormIndex: number | undefined = undefined; + + // mandatory form + materialSettingForm = this.formBuilder.group({ + materialSetting: this.formBuilder.array([]), + }, { updateOn: 'blur' }); + + // getter + get materialSetting() { + return this.materialSettingForm.get('materialSetting') as FormArray; + } + + constructor(private _materialService: MaterialService, private formBuilder: FormBuilder) {} async ngOnInit(): Promise { // do fetch material settings (await this._materialService.getFullMaterialDetail()).subscribe((data) => { this.allMaterials = data; - this.allMaterialsGroupedByTypes = this.ListCategory(); + this.allMaterialsGroupedByTypes = this.listCategory(); }); } // ------------------ Functions --------------------- // filter material by type - ListCategory = () => { + listCategory = () => { let catMap: { [key: string]: any[] } = { others: [], }; @@ -82,16 +96,93 @@ export class MaterialSettingsComponent implements OnInit { return catList; }; - // get material settings by id - async getMaterialSettingsById(id: number) { - // Implementation goes here - (await this._materialService.getMaterialSettingById(id)).subscribe( + + createMaterialSettingFormGroup(mat_set: MaterialSetting){ + return this.formBuilder.group({ + materialName: mat_set.materialName, + materialId: mat_set.materialId, + materialOtherName: mat_set.materialOtherName, + RawMaterialUnit: mat_set.RawMaterialUnit, + IceScreamBingsuChannel: mat_set.IceScreamBingsuChannel, + StringParam: mat_set.StringParam, + AlarmIDWhenOffline: mat_set.AlarmIDWhenOffline, + BeanChannel: mat_set.BeanChannel, + CanisterType: mat_set.CanisterType, + DrainTimer: mat_set.DrainTimer, + IsEquipment: mat_set.IsEquipment, + LeavesChannel: mat_set.LeavesChannel, + LowToOffline: mat_set.LowToOffline, + MaterialStatus: mat_set.MaterialStatus, + PowderChannel: mat_set.PowderChannel, + RefillUnitGram: mat_set.RefillUnitGram, + RefillUnitMilliliters: mat_set.RefillUnitMilliliters, + RefillUnitPCS: mat_set.RefillUnitPCS, + ScheduleDrainType: mat_set.ScheduleDrainType, + SodaChannel: mat_set.SodaChannel, + StockAdjust: mat_set.StockAdjust, + SyrupChannel: mat_set.SyrupChannel, + id: mat_set.id, + idAlternate: mat_set.idAlternate, + isUse: mat_set.isUse, + pay_rettry_max_count: mat_set.pay_rettry_max_count, + feed_mode: mat_set.feed_mode, + MaterialParameter: mat_set.MaterialParameter + }); + } + + // open material id modal + async openMaterialSettingModal(id: string){ + // set current material + (await this._materialService.getMaterialSettingById(parseInt(id))).subscribe( (data) => { this.currentMaterialSettings = data; - this.showMaterialSettingModal = true; - console.log('material setting', data); + + // do create form, if not exist + // - find matching form + // - if not exist, create new form + let pushableFlag = false; + let foundFlag = false; + // this.materialSetting.controls.forEach((control, index) => { + // console.log("different id", "checkon value", control.value,control.value.id, id); + + // if(foundFlag){ + // continue; + // } + + // if (control == undefined ||(control.value as any).id == id) { + // pushableFlag = false; + // foundFormIndex = index; + // } else { + // // set to last index, if not found. Meaning this is new form + // foundFormIndex = this.materialSetting.length; + // } + // }); + + // filter find index + let foundFormIndex = this.materialSetting.controls.findIndex((control) => { + return (control.value as any).id == id + }); + + console.log("found form index", foundFormIndex); + + if(foundFormIndex < 0){ + foundFormIndex = this.materialSetting.length - 1 < 0 ? 0 : this.materialSetting.length; + + pushableFlag = true; + } + + if(pushableFlag){ + this.materialSetting.push(this.createMaterialSettingFormGroup(data)); + console.log('push new form', this.materialSetting); + } + + // export index + this.currentFormIndex = foundFormIndex; + + console.log('material setting', data, "at index", foundFormIndex, "current form index", this.currentFormIndex); // console.log("keys of material settings", Object.keys(data)); console.log('material setting', data.isUse, typeof data.isUse); + (document.getElementById("material_settings_modal_"+id) as any)!.showModal(); } ); } diff --git a/client/src/app/features/recipes/recipe-details/recipe-details.component.html b/client/src/app/features/recipes/recipe-details/recipe-details.component.html index 9d36615..33a7339 100644 --- a/client/src/app/features/recipes/recipe-details/recipe-details.component.html +++ b/client/src/app/features/recipes/recipe-details/recipe-details.component.html @@ -133,7 +133,7 @@ >
diff --git a/client/src/app/features/recipes/recipe-details/recipe-list/recipe-list.component.html b/client/src/app/features/recipes/recipe-details/recipe-list/recipe-list.component.html index 4d6b6ee..5c7445b 100644 --- a/client/src/app/features/recipes/recipe-details/recipe-list/recipe-list.component.html +++ b/client/src/app/features/recipes/recipe-details/recipe-list/recipe-list.component.html @@ -13,8 +13,13 @@ *ngFor="let mat of recipeListData.controls; let i = index" > @@ -189,6 +194,10 @@ +
+ + +
@@ -396,7 +405,7 @@ [(ngModel)]="showMaterialSelector" />
diff --git a/client/src/app/features/toppings/toppings.component.ts b/client/src/app/features/toppings/toppings.component.ts index 1a52683..001460f 100644 --- a/client/src/app/features/toppings/toppings.component.ts +++ b/client/src/app/features/toppings/toppings.component.ts @@ -42,6 +42,7 @@ export class ToppingsComponent implements OnInit { // topping list keys toppingListKeys: string[] = []; + // modals controller showToppingBuilder: boolean = false; // forms @@ -55,10 +56,20 @@ export class ToppingsComponent implements OnInit { productCode: string | undefined; addingNewRecipeList: boolean = false; + // current data variables + currentMembersData: { [key: string]: any }[] | undefined = undefined; + currentMemberDataForm: FormGroup = this._formBuilder.group({ + currentMembersOfToppingGroup: this._formBuilder.array([]), + }); + get toppingGroup(): FormArray { return this.toppingGroupForm.get('toppingGroup') as FormArray; } + get currentMembersOfToppingGroup(): FormArray { + return this.currentMemberDataForm.get('currentMembersOfToppingGroup') as FormArray; + } + // ---------------------------------------------------------------------------- constructor( @@ -219,4 +230,68 @@ export class ToppingsComponent implements OnInit { return this.getMemberByGroupId(group).includes(member.toString()); }; + // topping list form structure + createToppingListForm = (data: ToppingList) => { + return this._formBuilder.group({ + ExtendID: data.ExtendID, + OnTOP: data.OnTOP, + MenuStatus: data.MenuStatus, + cashPrice: data.cashPrice, + disable: data.disable, + disable_by_cup: data.disable_by_cup, + disable_by_ice: data.disable_by_ice, + EncoderCount: data.EncoderCount, + id: data.id, + isUse: data.isUse, + isShow: data.isShow, + StringParam: data.StringParam, + name: data.name, + nonCashPrice: data.nonCashPrice, + otherName: data.otherName, + productCode: data.productCode, + recipes: data.recipes, + total_time: data.total_time, + total_weight: data.total_weight, + useGram: data.useGram, + weight_float: data.weight_float, + }) + } + + + + + // use when selected a group, show its member data + showToppingList = (groupID: string) => { + + // check empty + console.log("toppingList.empty", this.toppingLists, "groupMembersMap.empty", this.groupMembersMap); + + // do another mapping, just to make sure data is included + this.mapNameToMember(); + + if(this.currentMembersData != undefined){ + this.currentMembersData = undefined; + this.currentMembersOfToppingGroup.clear(); + } + + + let members = this.groupMembersMap[groupID]['members']; + members.forEach((member_id: string) => { + // get each member data from group + let member_data = this.getMemberData(groupID, member_id); + if(this.currentMembersData == undefined){ + this.currentMembersData = []; + } + this.currentMembersData!.push(member_data); + this.currentMembersOfToppingGroup.push(this.createToppingListForm(member_data)); + }); + // this.isShowToppingList = true; + + // query selector + let toppingListModal = document.getElementById('topping_list_modal') as any; + toppingListModal?.showModal(); + + console.log('current members data', this.currentMembersData); + console.log("current members of topping group", this.currentMembersOfToppingGroup); + } } diff --git a/server/data/data.go b/server/data/data.go index 2091222..c2a248f 100644 --- a/server/data/data.go +++ b/server/data/data.go @@ -499,6 +499,85 @@ func (d *Data) SetValuesToRecipe(base_recipe []models.Recipe01, recipe models.Re } } +func (d *Data) SetValuesToMaterialSetting(base_mat_setting []models.MaterialSetting, updated_mat_setting models.MaterialSetting) { + not_found := false + global_idx := 0 + + for index, v := range base_mat_setting { + // find matched id + if v.ID == updated_mat_setting.ID { + // change only changed values + for k, v := range updated_mat_setting.ToMap() { + if !reflect.DeepEqual(base_mat_setting[index].ToMap()[k], v) { + d.taoLogger.Log.Debug("SetValuesToMaterialSetting", zap.Any("key", k), zap.Any("old", base_mat_setting[index].ToMap()[k]), zap.Any("new", v)) + base_mat_setting[index].ToMap()[k] = v + } + } + } else { + not_found = true + global_idx = index + } + } + + // is new value + if not_found { + base_mat_setting[global_idx+1] = updated_mat_setting + } +} + +func (d *Data) SetValuesToToppingList(base_topping_list []models.ToppingList, updated_topping_list models.ToppingList) { + not_found := false + global_idx := 0 + + for index, v := range base_topping_list { + // find matched id + if v.ID == updated_topping_list.ID { + // change only changed values + for k, v := range updated_topping_list.ToMap() { + if !reflect.DeepEqual(base_topping_list[index].ToMap()[k], v) { + d.taoLogger.Log.Debug("SetValuesToToppingList", zap.Any("key", k), zap.Any("old", base_topping_list[index].ToMap()[k]), zap.Any("new", v)) + base_topping_list[index].ToMap()[k] = v + } + } + } else { + not_found = true + global_idx = index + } + } + + // is new value + if not_found { + base_topping_list[global_idx+1] = updated_topping_list + } +} + +func (d *Data) SetValuesToToppingGroupList(base_topping_group_list []models.ToppingGroup, updated_topping_group_list models.ToppingGroup) { + not_found := false + global_idx := 0 + + for index, v := range base_topping_group_list { + + // find matched id + if v.GroupID == updated_topping_group_list.GroupID { + // change only changed values + for k, v := range updated_topping_group_list.ToMap() { + if !reflect.DeepEqual(base_topping_group_list[index].ToMap()[k], v) { + d.taoLogger.Log.Debug("SetValuesToToppingGroup", zap.Any("key", k), zap.Any("old", base_topping_group_list[index].ToMap()[k]), zap.Any("new", v)) + base_topping_group_list[index].ToMap()[k] = v + } + } + } else { + not_found = true + global_idx = index + } + } + + // is new value + if not_found { + base_topping_group_list[global_idx+1] = updated_topping_group_list + } +} + func (d *Data) GetMaterialSetting(countryID, filename string) []models.MaterialSetting { result := make([]models.MaterialSetting, 0) diff --git a/server/models/recipe.go b/server/models/recipe.go index 3f1f475..d661831 100644 --- a/server/models/recipe.go +++ b/server/models/recipe.go @@ -57,6 +57,19 @@ type MaterialSetting struct { RawMaterialUnit string `json:"RawMaterialUnit"` } +func (r *MaterialSetting) ToMap() map[string]interface{} { + var m map[string]interface{} + recipeRecord, _ := json.Marshal(r) + json.Unmarshal(recipeRecord, &m) + return m +} + +func (r *MaterialSetting) FromMap(m MaterialSetting) MaterialSetting { + recipeRecord, _ := json.Marshal(m) + json.Unmarshal(recipeRecord, &r) + return *r +} + type Recipe01 struct { Description string `json:"Description"` ExtendID int `json:"ExtendID"` @@ -140,6 +153,19 @@ type ToppingGroup struct { OtherName string `json:"otherName"` } +func (r *ToppingGroup) ToMap() map[string]interface{} { + var m map[string]interface{} + recipeRecord, _ := json.Marshal(r) + json.Unmarshal(recipeRecord, &m) + return m +} + +func (r *ToppingGroup) FromMap(m map[string]interface{}) ToppingGroup { + recipeRecord, _ := json.Marshal(m) + json.Unmarshal(recipeRecord, &r) + return *r +} + type ToppingList struct { ExtendID int `json:"ExtendID"` OnTOP bool `json:"OnTOP"` @@ -163,3 +189,16 @@ type ToppingList struct { UseGram bool `json:"useGram"` Weight_float int `json:"weight_float"` } + +func (r *ToppingList) ToMap() map[string]interface{} { + var m map[string]interface{} + recipeRecord, _ := json.Marshal(r) + json.Unmarshal(recipeRecord, &m) + return m +} + +func (r *ToppingList) FromMap(m map[string]interface{}) ToppingList { + recipeRecord, _ := json.Marshal(m) + json.Unmarshal(recipeRecord, &r) + return *r +} diff --git a/server/routers/recipe.go b/server/routers/recipe.go index 186fe32..567c61b 100644 --- a/server/routers/recipe.go +++ b/server/routers/recipe.go @@ -504,6 +504,34 @@ func (rr *RecipeRouter) updateRecipe(w http.ResponseWriter, r *http.Request) { }) } +func (rr *RecipeRouter) updateMaterialSetting(w http.ResponseWriter, r *http.Request) { + w.Header().Add("Content-Type", "application/json") + + country := r.URL.Query().Get("country") + filename := r.URL.Query().Get("filename") + + rr.taoLogger.Log.Debug("RecipeRouter.updateMaterialSetting", zap.Any("country", country), zap.Any("filename", filename)) + + countryID, err := rr.data.GetCountryIDByName(country) + if err != nil { + http.Error(w, fmt.Sprintf("Country Name: %s not found!!!", country), http.StatusNotFound) + return + } + + updateMutex.Lock() + defer updateMutex.Unlock() + // get material setting + materialSetting := rr.data.GetMaterialSetting(countryID, filename) + + if len(materialSetting) == 0 { + http.Error(w, fmt.Sprintf("Recipe country: %s file: %s found empty settings.", country, filename), http.StatusNotFound) + return + } + + // TODO: create commit and set change + +} + func (rr *RecipeRouter) getSavedRecipes(w http.ResponseWriter, r *http.Request) { file_version := chi.URLParam(r, "filename_version_only")