From 71183fa17c7522098900b9a8997e4caf22eafc57 Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 9 Oct 2023 12:10:06 +0800 Subject: [PATCH] [add] page config --- sui/api/process_test.go | 9 +- sui/core/editor.go | 5 +- sui/core/interfaces.go | 3 +- sui/core/json.go | 61 +++++++++ sui/core/json_test.go | 228 ++++++++++++++++++++++++++++++++ sui/core/page.go | 30 ++--- sui/core/types.go | 46 ++++++- sui/storages/local/page.go | 48 +++++++ sui/storages/local/page_test.go | 39 ++++++ 9 files changed, 441 insertions(+), 28 deletions(-) create mode 100644 sui/core/json.go create mode 100644 sui/core/json_test.go diff --git a/sui/api/process_test.go b/sui/api/process_test.go index 5e6b1a33..dad09d18 100644 --- a/sui/api/process_test.go +++ b/sui/api/process_test.go @@ -542,8 +542,9 @@ func TestEditorRender(t *testing.T) { t.Fatal(err) } - assert.IsType(t, &core.ResponseEditor{}, res) - assert.NotEmpty(t, res.(*core.ResponseEditor).HTML) + assert.IsType(t, &core.ResponseEditorRender{}, res) + assert.NotEmpty(t, res.(*core.ResponseEditorRender).HTML) + assert.NotEmpty(t, res.(*core.ResponseEditorRender).Config) } func TestEditorRenderWithQuery(t *testing.T) { @@ -563,8 +564,8 @@ func TestEditorRenderWithQuery(t *testing.T) { t.Fatal(err) } - assert.IsType(t, &core.ResponseEditor{}, res) - assert.NotEmpty(t, res.(*core.ResponseEditor).HTML) + assert.IsType(t, &core.ResponseEditorRender{}, res) + assert.NotEmpty(t, res.(*core.ResponseEditorRender).HTML) } func TestEditorPageSource(t *testing.T) { diff --git a/sui/core/editor.go b/sui/core/editor.go index caeefb9a..2b11c9c9 100644 --- a/sui/core/editor.go +++ b/sui/core/editor.go @@ -7,15 +7,16 @@ import ( ) // EditorRender render HTML for the editor -func (page *Page) EditorRender(request *Request) (*ResponseEditor, error) { +func (page *Page) EditorRender(request *Request) (*ResponseEditorRender, error) { html := page.Codes.HTML.Code - res := &ResponseEditor{ + res := &ResponseEditorRender{ HTML: "", CSS: page.Codes.CSS.Code, Scripts: []string{}, Styles: []string{}, Warnings: []string{}, + Config: page.GetConfig(), Setting: map[string]interface{}{}, } diff --git a/sui/core/interfaces.go b/sui/core/interfaces.go index ee80f9ad..59caf9ae 100644 --- a/sui/core/interfaces.go +++ b/sui/core/interfaces.go @@ -38,11 +38,12 @@ type IPage interface { Load() error Get() *Page + GetConfig() *PageConfig Save(request *RequestSource) error SaveTemp(request *RequestSource) error Remove() error - EditorRender(request *Request) (*ResponseEditor, error) + EditorRender(request *Request) (*ResponseEditorRender, error) EditorPageSource() SourceData EditorScriptSource() SourceData EditorStyleSource() SourceData diff --git a/sui/core/json.go b/sui/core/json.go new file mode 100644 index 00000000..9bc5752f --- /dev/null +++ b/sui/core/json.go @@ -0,0 +1,61 @@ +package core + +import ( + jsoniter "github.com/json-iterator/go" +) + +// UnmarshalJSON Custom JSON unmarshal function for PageMock +func (mock *PageMock) UnmarshalJSON(data []byte) error { + + if mock == nil { + return nil + } + + type Alias struct { + Method string `json:"method"` + Params map[string]string `json:"params,omitempty"` + Query map[string]interface{} `json:"query,omitempty"` + Headers map[string]interface{} `json:"headers,omitempty"` + Body interface{} `json:"body,omitempty"` + } + + aux := &Alias{} + if err := jsoniter.Unmarshal(data, &aux); err != nil { + return err + } + + method := aux.Method + if method == "" { + method = "GET" + } + mock.Body = aux.Body + mock.Method = method + mock.Params = aux.Params + mock.Query = convertRecordToMap(aux.Query) + mock.Headers = convertRecordToMap(aux.Headers) + return nil +} + +// Helper function to convert TypeScript Record to map[string][]string +func convertRecordToMap(record map[string]interface{}) map[string][]string { + if record == nil { + return nil + } + + result := make(map[string][]string) + for key, value := range record { + switch v := value.(type) { + case string: + result[key] = []string{v} + case []interface{}: + strValues := make([]string, len(v)) + for i, item := range v { + if str, ok := item.(string); ok { + strValues[i] = str + } + } + result[key] = strValues + } + } + return result +} diff --git a/sui/core/json_test.go b/sui/core/json_test.go new file mode 100644 index 00000000..3a958086 --- /dev/null +++ b/sui/core/json_test.go @@ -0,0 +1,228 @@ +package core + +import ( + "reflect" + "testing" + + jsoniter "github.com/json-iterator/go" +) + +func TestRequestSourceUnmarshalJSON(t *testing.T) { + testCases := []struct { + name string + jsonData string + expected *RequestSource + shouldFail bool + }{ + { + name: "Valid JSON with string query and headers", + jsonData: `{ + "uid": "123", + "mock": { + "method": "GET", + "query": { + "q1": "value1", + "q2": "value2" + }, + "headers": { + "header1": "value1", + "header2": "value2" + } + } + }`, + expected: &RequestSource{ + UID: "123", + Mock: &PageMock{ + Method: "GET", + Query: map[string][]string{ + "q1": {"value1"}, + "q2": {"value2"}, + }, + Headers: map[string][]string{ + "header1": {"value1"}, + "header2": {"value2"}, + }, + }, + }, + shouldFail: false, + }, + { + name: "Valid JSON with array query and headers", + jsonData: `{ + "uid": "456", + "mock": { + "method": "POST", + "query": { + "q1": ["value1", "value2"], + "q2": ["value3"] + }, + "headers": { + "header1": ["value1", "value2"], + "header2": "value3" + } + } + }`, + expected: &RequestSource{ + UID: "456", + Mock: &PageMock{ + Method: "POST", + Query: map[string][]string{ + "q1": {"value1", "value2"}, + "q2": {"value3"}, + }, + Headers: map[string][]string{ + "header1": {"value1", "value2"}, + "header2": {"value3"}, + }, + }, + }, + shouldFail: false, + }, + { + name: "Valid JSON with invalid query", + jsonData: `{ + "uid": "789", + "mock": { + "method": "PUT", + "query":"1203" + } + }`, + expected: nil, + shouldFail: true, + }, + // Add more test cases here to cover other scenarios. + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + var requestSource RequestSource + err := jsoniter.Unmarshal([]byte(testCase.jsonData), &requestSource) + + if testCase.shouldFail { + if err == nil { + t.Errorf("%s: Expected unmarshal to fail, but it succeeded", testCase.name) + } + } else { + if err != nil { + t.Errorf("Unmarshal failed: %v", err) + } + if !reflect.DeepEqual(requestSource, *testCase.expected) { + t.Errorf("Unmarshaled result does not match expected result") + } + } + }) + } +} + +func TestPageConfigUnmarshalJSON(t *testing.T) { + testCases := []struct { + name string + jsonData string + expected *PageConfig + shouldFail bool + }{ + { + name: "Valid JSON with PageSetting and PageMock", + jsonData: `{ + "title": "Page Title", + "mock": { + "method": "GET", + "query": { + "q1": "value1", + "q2": "value2" + }, + "headers": { + "header1": "value1", + "header2": "value2" + } + } + }`, + expected: &PageConfig{ + PageSetting: PageSetting{ + Title: "Page Title", + }, + Mock: &PageMock{ + Method: "GET", + Query: map[string][]string{ + "q1": {"value1"}, + "q2": {"value2"}, + }, + Headers: map[string][]string{ + "header1": {"value1"}, + "header2": {"value2"}, + }, + }, + }, + shouldFail: false, + }, + { + name: "Valid JSON with PageSetting only", + jsonData: `{ + "title": "Page Title" + }`, + expected: &PageConfig{ + PageSetting: PageSetting{ + Title: "Page Title", + }, + Mock: nil, + }, + shouldFail: false, + }, + { + name: "Valid JSON with PageMock only", + jsonData: `{ + "mock": { + "method": "GET", + "query": { + "q1": "value1", + "q2": "value2" + } + } + }`, + expected: &PageConfig{ + Mock: &PageMock{ + Method: "GET", + Query: map[string][]string{ + "q1": {"value1"}, + "q2": {"value2"}, + }, + }, + }, + shouldFail: false, + }, + { + name: "Valid JSON with invalid query", + jsonData: `{ + "title": "Page Title", + "mock": { + "method": "PUT", + "query": "invalid query" + } + }`, + expected: nil, + shouldFail: true, + }, + // Add more test cases here to cover other scenarios. + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + var pageConfig PageConfig + err := jsoniter.Unmarshal([]byte(testCase.jsonData), &pageConfig) + + if testCase.shouldFail { + if err == nil { + t.Errorf("%s: Expected unmarshal to fail, but it succeeded", testCase.name) + } + } else { + + if err != nil { + t.Errorf("Unmarshal failed: %v", err) + } + if !reflect.DeepEqual(pageConfig, *testCase.expected) { + t.Errorf("Unmarshaled result does not match expected result") + } + } + }) + } +} diff --git a/sui/core/page.go b/sui/core/page.go index c113f2b8..c4b4f1a2 100644 --- a/sui/core/page.go +++ b/sui/core/page.go @@ -1,24 +1,20 @@ package core +import jsoniter "github.com/json-iterator/go" + // Get get the base info func (page *Page) Get() *Page { return page } -// GetHTML get the html -func (page *Page) GetHTML() {} - -// GetScript get the script -func (page *Page) GetScript() {} - -// GetStyle get the style -func (page *Page) GetStyle() {} - -// GetData get the data -func (page *Page) GetData() {} - -// SaveTemp save the temp -func (page *Page) SaveTemp() {} - -// Save the page -func (page *Page) Save() {} +// GetConfig get the config +func (page *Page) GetConfig() *PageConfig { + if page.Config == nil && page.Codes.CONF.Code != "" { + var config PageConfig + err := jsoniter.Unmarshal([]byte(page.Codes.CONF.Code), &config) + if err == nil { + page.Config = &config + } + } + return page.Config +} diff --git a/sui/core/types.go b/sui/core/types.go index 9af11103..36742150 100644 --- a/sui/core/types.go +++ b/sui/core/types.go @@ -12,6 +12,7 @@ type DSL struct { type Page struct { Route string `json:"route"` Name string `json:"name,omitempty"` + Config *PageConfig `json:"-"` Path string `json:"-"` Codes SourceCodes `json:"-"` Document []byte `json:"-"` @@ -79,8 +80,8 @@ type Request struct { Payload map[string]interface{} `json:"payload,omitempty"` Query map[string][]string `json:"query,omitempty"` Params map[string]string `json:"params,omitempty"` - Headers []string `json:"headers,omitempty"` - Body []byte `json:"body,omitempty"` + Headers map[string][]string `json:"headers,omitempty"` + Body interface{} `json:"body,omitempty"` Theme string `json:"theme,omitempty"` Locale string `json:"locale,omitempty"` } @@ -94,23 +95,28 @@ type RequestSource struct { Script *SourceData `json:"script,omitempty"` Data *SourceData `json:"data,omitempty"` Board *BoardSourceData `json:"board,omitempty"` + Mock *PageMock `json:"mock,omitempty"` + Setting *PageSetting `json:"setting,omitempty"` NeedToSave struct { Page bool `json:"page,omitempty"` Style bool `json:"style,omitempty"` Script bool `json:"script,omitempty"` Data bool `json:"data,omitempty"` Board bool `json:"board,omitempty"` + Mock bool `json:"mock,omitempty"` + Setting bool `json:"setting,omitempty"` Validate bool `json:"validate,omitempty"` } `json:"needToSave,omitempty"` } -// ResponseEditor is the struct for the response -type ResponseEditor struct { +// ResponseEditorRender is the struct for the response +type ResponseEditorRender struct { HTML string `json:"html,omitempty"` CSS string `json:"css,omitempty"` Scripts []string `json:"scripts,omitempty"` Styles []string `json:"styles,omitempty"` Setting map[string]interface{} `json:"setting,omitempty"` + Config *PageConfig `json:"config,omitempty"` Warnings []string `json:"warnings,omitempty"` } @@ -126,6 +132,37 @@ type BoardSourceData struct { Style string `json:"style,omitempty"` } +// PageMock is the struct for the request +type PageMock struct { + Method string `json:"method,omitempty"` + Params map[string]string `json:"params,omitempty"` + Query map[string][]string `json:"query,omitempty"` + Headers map[string][]string `json:"headers,omitempty"` + Body interface{} `json:"body,omitempty"` +} + +// PageConfig is the struct for the page config +type PageConfig struct { + PageSetting `json:",omitempty"` + Mock *PageMock `json:"mock,omitempty"` +} + +// PageSetting is the struct for the page setting +type PageSetting struct { + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + SEO *PageSEO `json:"seo,omitempty"` +} + +// PageSEO is the struct for the page seo +type PageSEO struct { + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + Keywords string `json:"keywords,omitempty"` + Image string `json:"image,omitempty"` + URL string `json:"url,omitempty"` +} + // SourceCodes is the struct for the page codes type SourceCodes struct { HTML Source `json:"-"` @@ -134,6 +171,7 @@ type SourceCodes struct { TS Source `json:"-"` LESS Source `json:"-"` DATA Source `json:"-"` + CONF Source `json:"-"` } // Source is the struct for the source diff --git a/sui/storages/local/page.go b/sui/storages/local/page.go index b6f7d30e..2576a908 100644 --- a/sui/storages/local/page.go +++ b/sui/storages/local/page.go @@ -5,6 +5,7 @@ import ( "path/filepath" "strings" + jsoniter "github.com/json-iterator/go" "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/sui/core" ) @@ -221,6 +222,7 @@ func (tmpl *Template) CreatePage(route string) (core.IPage, error) { JS: core.Source{File: fmt.Sprintf("%s.js", name)}, TS: core.Source{File: fmt.Sprintf("%s.ts", name)}, LESS: core.Source{File: fmt.Sprintf("%s.less", name)}, + CONF: core.Source{File: fmt.Sprintf("%s.config", name)}, }, }, }, nil @@ -272,6 +274,7 @@ func (tmpl *Template) getPage(route, file string) (core.IPage, error) { DATA: core.Source{File: fmt.Sprintf("%s.json", name)}, TS: core.Source{File: fmt.Sprintf("%s.ts", name)}, LESS: core.Source{File: fmt.Sprintf("%s.less", name)}, + CONF: core.Source{File: fmt.Sprintf("%s.config", name)}, }, }, }, nil @@ -345,6 +348,16 @@ func (page *Page) Load() error { page.Codes.DATA.Code = string(dataCode) } + // Read the config code + confFile := filepath.Join(page.Path, page.Codes.CONF.File) + if exist, _ := page.tmpl.local.fs.Exists(confFile); exist { + confCode, err := page.tmpl.local.fs.ReadFile(confFile) + if err != nil { + return err + } + page.Codes.CONF.Code = string(confCode) + } + // Set the page document page.Document = page.tmpl.Document return nil @@ -422,6 +435,13 @@ func (page *Page) save(path string, request *core.RequestSource) error { } } + if request.NeedToSave.Setting || request.NeedToSave.Mock { + err := page.saveSetting(path, request.Setting, request.Mock) + if err != nil { + return err + } + } + return nil } @@ -486,6 +506,34 @@ func (page *Page) saveData(path string, src *core.SourceData) error { return err } +func (page *Page) saveSetting(path string, setting *core.PageSetting, mock *core.PageMock) error { + + config := map[string]interface{}{} + if setting != nil { + err := jsoniter.Unmarshal([]byte(page.Codes.CONF.Code), &config) + if err != nil { + return err + } + } + + if mock != nil { + config["mock"] = mock + } + + configBytes, err := jsoniter.MarshalIndent(config, "", " ") + if err != nil { + return err + } + + if len(config) > 0 { + dataFile := filepath.Join(path, page.Codes.CONF.File) + _, err = page.tmpl.local.fs.WriteFile(dataFile, configBytes, 0644) + return err + } + + return nil +} + // AssetScript get the script func (page *Page) AssetScript() (*core.Asset, error) { diff --git a/sui/storages/local/page_test.go b/sui/storages/local/page_test.go index 30d60792..0844e746 100644 --- a/sui/storages/local/page_test.go +++ b/sui/storages/local/page_test.go @@ -42,6 +42,7 @@ func TestTemplatePages(t *testing.T) { assert.Equal(t, name+".less", page.Codes.LESS.File) assert.Equal(t, name+".ts", page.Codes.TS.File) assert.Equal(t, name+".json", page.Codes.DATA.File) + assert.Equal(t, name+".config", page.Codes.CONF.File) } } @@ -444,6 +445,44 @@ func TestPageSaveTempData(t *testing.T) { assert.Nil(t, err) } +func TestPageSaveTempSetting(t *testing.T) { + tests := prepare(t) + defer clean() + + tmpl, err := tests.Demo.GetTemplate("tech-blue") + if err != nil { + t.Fatalf("GetTemplate error: %v", err) + } + + const payload = `{ + "page": null, + "style": null, + "script": null, + "setting": { "title": "Home Page | {{ $global.title }}" }, + "mock": { "params": { "id": "1" } }, + "needToSave": { + "page": false, + "style": false, + "script": false, + "mock": true, + "setting": true, + "board": false, + "validate": true + } + }` + + req := &core.RequestSource{UID: "19e09e7e-9e19-44c1-bbab-2a55c51c9df3"} + jsoniter.Unmarshal([]byte(payload), &req) + + page, err := tmpl.Page("/index") + if err != nil { + t.Fatalf("Page error: %v", err) + } + + err = page.SaveTemp(req) + assert.Nil(t, err) +} + func TestPageSave(t *testing.T) { tests := prepare(t) defer clean()