diff --git a/server/api/read/models/de_web_info.go b/server/api/read/models/de_web_info.go new file mode 100644 index 00000000..5de16cda --- /dev/null +++ b/server/api/read/models/de_web_info.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// DeWebInfo de web info +// +// swagger:model DeWebInfo +type DeWebInfo struct { + + // app + App string `json:"app,omitempty"` + + // misc + Misc interface{} `json:"misc,omitempty"` + + // version + Version string `json:"version,omitempty"` +} + +// Validate validates this de web info +func (m *DeWebInfo) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this de web info based on context it is used +func (m *DeWebInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *DeWebInfo) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *DeWebInfo) UnmarshalBinary(b []byte) error { + var res DeWebInfo + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/server/api/read/restapi/embedded_spec.go b/server/api/read/restapi/embedded_spec.go index 0f01f10f..2ee7c46f 100644 --- a/server/api/read/restapi/embedded_spec.go +++ b/server/api/read/restapi/embedded_spec.go @@ -38,6 +38,22 @@ func init() { } } }, + "/__deweb_info": { + "get": { + "produces": [ + "application/json" + ], + "operationId": "getDeWebInfo", + "responses": { + "200": { + "description": "Successful response", + "schema": { + "$ref": "#/definitions/DeWebInfo" + } + } + } + } + }, "/{resource}": { "get": { "operationId": "getResource", @@ -64,6 +80,21 @@ func init() { } }, "definitions": { + "DeWebInfo": { + "type": "object", + "properties": { + "app": { + "type": "string" + }, + "misc": { + "type": "object", + "additionalProperties": true + }, + "version": { + "type": "string" + } + } + }, "Error": { "type": "object", "properties": { @@ -95,6 +126,22 @@ func init() { } } }, + "/__deweb_info": { + "get": { + "produces": [ + "application/json" + ], + "operationId": "getDeWebInfo", + "responses": { + "200": { + "description": "Successful response", + "schema": { + "$ref": "#/definitions/DeWebInfo" + } + } + } + } + }, "/{resource}": { "get": { "operationId": "getResource", @@ -121,6 +168,21 @@ func init() { } }, "definitions": { + "DeWebInfo": { + "type": "object", + "properties": { + "app": { + "type": "string" + }, + "misc": { + "type": "object", + "additionalProperties": true + }, + "version": { + "type": "string" + } + } + }, "Error": { "type": "object", "properties": { diff --git a/server/api/read/restapi/operations/de_web_api.go b/server/api/read/restapi/operations/de_web_api.go index 8dfe94ee..8e8afc5a 100644 --- a/server/api/read/restapi/operations/de_web_api.go +++ b/server/api/read/restapi/operations/de_web_api.go @@ -45,6 +45,9 @@ func NewDeWebAPI(spec *loads.Document) *DeWebAPI { DefaultPageHandler: DefaultPageHandlerFunc(func(params DefaultPageParams) middleware.Responder { return middleware.NotImplemented("operation DefaultPage has not yet been implemented") }), + GetDeWebInfoHandler: GetDeWebInfoHandlerFunc(func(params GetDeWebInfoParams) middleware.Responder { + return middleware.NotImplemented("operation GetDeWebInfo has not yet been implemented") + }), GetResourceHandler: GetResourceHandlerFunc(func(params GetResourceParams) middleware.Responder { return middleware.NotImplemented("operation GetResource has not yet been implemented") }), @@ -86,6 +89,8 @@ type DeWebAPI struct { // DefaultPageHandler sets the operation handler for the default page operation DefaultPageHandler DefaultPageHandler + // GetDeWebInfoHandler sets the operation handler for the get de web info operation + GetDeWebInfoHandler GetDeWebInfoHandler // GetResourceHandler sets the operation handler for the get resource operation GetResourceHandler GetResourceHandler @@ -168,6 +173,9 @@ func (o *DeWebAPI) Validate() error { if o.DefaultPageHandler == nil { unregistered = append(unregistered, "DefaultPageHandler") } + if o.GetDeWebInfoHandler == nil { + unregistered = append(unregistered, "GetDeWebInfoHandler") + } if o.GetResourceHandler == nil { unregistered = append(unregistered, "GetResourceHandler") } @@ -266,6 +274,10 @@ func (o *DeWebAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/__deweb_info"] = NewGetDeWebInfo(o.context, o.GetDeWebInfoHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/{resource}"] = NewGetResource(o.context, o.GetResourceHandler) } diff --git a/server/api/read/restapi/operations/get_de_web_info.go b/server/api/read/restapi/operations/get_de_web_info.go new file mode 100644 index 00000000..92e6714e --- /dev/null +++ b/server/api/read/restapi/operations/get_de_web_info.go @@ -0,0 +1,56 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetDeWebInfoHandlerFunc turns a function with the right signature into a get de web info handler +type GetDeWebInfoHandlerFunc func(GetDeWebInfoParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetDeWebInfoHandlerFunc) Handle(params GetDeWebInfoParams) middleware.Responder { + return fn(params) +} + +// GetDeWebInfoHandler interface for that can handle valid get de web info params +type GetDeWebInfoHandler interface { + Handle(GetDeWebInfoParams) middleware.Responder +} + +// NewGetDeWebInfo creates a new http.Handler for the get de web info operation +func NewGetDeWebInfo(ctx *middleware.Context, handler GetDeWebInfoHandler) *GetDeWebInfo { + return &GetDeWebInfo{Context: ctx, Handler: handler} +} + +/* + GetDeWebInfo swagger:route GET /__deweb_info getDeWebInfo + +GetDeWebInfo get de web info API +*/ +type GetDeWebInfo struct { + Context *middleware.Context + Handler GetDeWebInfoHandler +} + +func (o *GetDeWebInfo) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetDeWebInfoParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/server/api/read/restapi/operations/get_de_web_info_parameters.go b/server/api/read/restapi/operations/get_de_web_info_parameters.go new file mode 100644 index 00000000..335aa19a --- /dev/null +++ b/server/api/read/restapi/operations/get_de_web_info_parameters.go @@ -0,0 +1,46 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" +) + +// NewGetDeWebInfoParams creates a new GetDeWebInfoParams object +// +// There are no default values defined in the spec. +func NewGetDeWebInfoParams() GetDeWebInfoParams { + + return GetDeWebInfoParams{} +} + +// GetDeWebInfoParams contains all the bound params for the get de web info operation +// typically these are obtained from a http.Request +// +// swagger:parameters getDeWebInfo +type GetDeWebInfoParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetDeWebInfoParams() beforehand. +func (o *GetDeWebInfoParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/server/api/read/restapi/operations/get_de_web_info_responses.go b/server/api/read/restapi/operations/get_de_web_info_responses.go new file mode 100644 index 00000000..c9d066da --- /dev/null +++ b/server/api/read/restapi/operations/get_de_web_info_responses.go @@ -0,0 +1,59 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/massalabs/deweb-server/api/read/models" +) + +// GetDeWebInfoOKCode is the HTTP code returned for type GetDeWebInfoOK +const GetDeWebInfoOKCode int = 200 + +/* +GetDeWebInfoOK Successful response + +swagger:response getDeWebInfoOK +*/ +type GetDeWebInfoOK struct { + + /* + In: Body + */ + Payload *models.DeWebInfo `json:"body,omitempty"` +} + +// NewGetDeWebInfoOK creates GetDeWebInfoOK with default headers values +func NewGetDeWebInfoOK() *GetDeWebInfoOK { + + return &GetDeWebInfoOK{} +} + +// WithPayload adds the payload to the get de web info o k response +func (o *GetDeWebInfoOK) WithPayload(payload *models.DeWebInfo) *GetDeWebInfoOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get de web info o k response +func (o *GetDeWebInfoOK) SetPayload(payload *models.DeWebInfo) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetDeWebInfoOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/server/api/read/restapi/operations/get_de_web_info_urlbuilder.go b/server/api/read/restapi/operations/get_de_web_info_urlbuilder.go new file mode 100644 index 00000000..3674c6d2 --- /dev/null +++ b/server/api/read/restapi/operations/get_de_web_info_urlbuilder.go @@ -0,0 +1,84 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package operations + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" +) + +// GetDeWebInfoURL generates an URL for the get de web info operation +type GetDeWebInfoURL struct { + _basePath string +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetDeWebInfoURL) WithBasePath(bp string) *GetDeWebInfoURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetDeWebInfoURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetDeWebInfoURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/__deweb_info" + + _basePath := o._basePath + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetDeWebInfoURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetDeWebInfoURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetDeWebInfoURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetDeWebInfoURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetDeWebInfoURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetDeWebInfoURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/server/api/readAPI-V0.yml b/server/api/readAPI-V0.yml index bca15171..56d9865a 100644 --- a/server/api/readAPI-V0.yml +++ b/server/api/readAPI-V0.yml @@ -28,6 +28,17 @@ paths: responses: "200": description: Shows the default page + + /__deweb_info: + get: + operationId: getDeWebInfo + produces: + - application/json + responses: + "200": + description: Successful response + schema: + $ref: "#/definitions/DeWebInfo" definitions: Error: @@ -35,3 +46,13 @@ definitions: properties: message: type: string + DeWebInfo: + type: object + properties: + app: + type: string + version: + type: string + misc: + type: object + additionalProperties: true diff --git a/server/int/api/api.go b/server/int/api/api.go index 9215ba40..aa33cfa3 100644 --- a/server/int/api/api.go +++ b/server/int/api/api.go @@ -62,4 +62,5 @@ func (a *API) configureAPI() { a.dewebAPI.GetResourceHandler = operations.GetResourceHandlerFunc(getResourceHandler) a.dewebAPI.DefaultPageHandler = operations.DefaultPageHandlerFunc(defaultPageHandler) + a.dewebAPI.GetDeWebInfoHandler = NewDewebInfo(a.conf.MiscPublicInfoJson) } diff --git a/server/int/api/config/config.go b/server/int/api/config/config.go index 7a331112..131c9acd 100644 --- a/server/int/api/config/config.go +++ b/server/int/api/config/config.go @@ -7,7 +7,7 @@ import ( "github.com/massalabs/deweb-server/int/utils" pkgConfig "github.com/massalabs/deweb-server/pkg/config" msConfig "github.com/massalabs/station/int/config" - "gopkg.in/yaml.v2" + yaml "gopkg.in/yaml.v2" ) const ( @@ -30,17 +30,19 @@ type ServerConfig struct { SiteRAMCacheMaxItems uint64 SiteDiskCacheMaxItems uint64 FileListCacheDurationSeconds int + MiscPublicInfoJson interface{} } type yamlServerConfig struct { - Domain string `yaml:"domain"` - NetworkNodeURL string `yaml:"network_node_url"` - APIPort int `yaml:"api_port"` - AllowList []string `yaml:"allow_list"` - BlockList []string `yaml:"block_list"` - SiteRAMCacheMaxItems uint64 `yaml:"site_ram_cache_max_items"` - SiteDiskCacheMaxItems uint64 `yaml:"site_disk_cache_max_items"` - FileListCacheDurationSeconds int `yaml:"file_list_cache_duration_seconds"` + Domain string `yaml:"domain"` + NetworkNodeURL string `yaml:"network_node_url"` + APIPort int `yaml:"api_port"` + AllowList []string `yaml:"allow_list"` + BlockList []string `yaml:"block_list"` + SiteRAMCacheMaxItems uint64 `yaml:"site_ram_cache_max_items"` + SiteDiskCacheMaxItems uint64 `yaml:"site_disk_cache_max_items"` + FileListCacheDurationSeconds int `yaml:"file_list_cache_duration_seconds"` + MiscPublicInfoJson interface{} `yaml:"misc_public_info"` } func DefaultConfig() *ServerConfig { @@ -55,6 +57,7 @@ func DefaultConfig() *ServerConfig { SiteRAMCacheMaxItems: DefaultMaxRAMEntries, SiteDiskCacheMaxItems: DefaultMaxDiskEntries, FileListCacheDurationSeconds: DefaultFileListCacheDurationSeconds, + MiscPublicInfoJson: map[string]interface{}{}, } } @@ -117,5 +120,37 @@ func LoadServerConfig(configPath string) (*ServerConfig, error) { SiteRAMCacheMaxItems: yamlConf.SiteRAMCacheMaxItems, SiteDiskCacheMaxItems: yamlConf.SiteDiskCacheMaxItems, FileListCacheDurationSeconds: yamlConf.FileListCacheDurationSeconds, + MiscPublicInfoJson: convertYamlMisc2Json(yamlConf.MiscPublicInfoJson), }, nil } + +/* +convertYamlMisc2Json convert the config's "misc" json field from a +map[interface{}]interface{} (as unmarshaled by yaml.Unmarshal function) +format to a map[string]interface{} one. +The input is expected to represent valid json. +*/ +func convertYamlMisc2Json(input interface{}) interface{} { + switch x := input.(type) { + case map[interface{}]interface{}: + result := make(map[string]interface{}) + for k, v := range x { + result[fmt.Sprint(k)] = convertYamlMisc2Json(v) + } + + return result + + case []interface{}: + for i, val := range x { + x[i] = convertYamlMisc2Json(val) + } + + return x + + case string: + return input.(string) + + default: + return input + } +} diff --git a/server/int/api/config/config_test.go b/server/int/api/config/config_test.go new file mode 100644 index 00000000..5d2bbb76 --- /dev/null +++ b/server/int/api/config/config_test.go @@ -0,0 +1,97 @@ +package config + +import ( + "encoding/json" + "testing" +) + +func TestConvertYamlMisc2Json(t *testing.T) { + // Define test cases + testCases := []struct { + name string + input interface{} + expectedJSON string + }{ + { + name: "Simple YAML", + input: map[interface{}]interface{}{ + "key1": "value1", + "key2": 1, + }, + expectedJSON: `{"key1":"value1","key2":1}`, + }, + { + name: "Empty string JSON", + input: ``, + expectedJSON: `""`, + }, + { + name: "non empty string", + input: "test", + expectedJSON: `"test"`, + }, + { + name: "Empty bracket json", + input: map[interface{}]interface{}{}, + expectedJSON: `{}`, + }, + { + name: "nil json", + input: nil, + expectedJSON: `null`, + }, + { + name: "number json", + input: 1, + expectedJSON: "1", + }, + { + name: "Array json", + input: []interface{}{ + "item1", + "item2", + }, + expectedJSON: `["item1","item2"]`, + }, + { + name: "complex json", + input: map[interface{}]interface{}{ + "key1": "value1", + "key2": map[interface{}]interface{}{ + "key3": "value3", + "key4": []interface{}{ + "item1", + "item2", + map[interface{}]interface{}{ + "key10": "value10", + }, + }, + }, + "key5": map[interface{}]interface{}{ + "key6": 1, + "key7": map[interface{}]interface{}{ + "key8": "value8", + "key9": "value9", + }, + }, + }, + expectedJSON: `{"key1":"value1","key2":{"key3":"value3","key4":["item1","item2",{"key10":"value10"}]},"key5":{"key6":1,"key7":{"key8":"value8","key9":"value9"}}}`, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + res := convertYamlMisc2Json(tc.input) + + jsonOutput, err := json.Marshal(res) + if err != nil { + t.Errorf("FAIL test %s: Failed to marshal the JSON: %v", tc.name, err) + } + + // Compare the output with the expected result + if string(jsonOutput) != tc.expectedJSON { + t.Errorf("FAIL test %s: Expected JSON: %s, but got: %s", tc.name, tc.expectedJSON, jsonOutput) + } + }) + } +} diff --git a/server/int/api/handlers.go b/server/int/api/handlers.go index ce0778a8..e0170107 100644 --- a/server/int/api/handlers.go +++ b/server/int/api/handlers.go @@ -6,7 +6,9 @@ import ( "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" + "github.com/massalabs/deweb-server/api/read/models" "github.com/massalabs/deweb-server/api/read/restapi/operations" + config "github.com/massalabs/station/int/config" "github.com/massalabs/station/pkg/logger" ) @@ -27,3 +29,26 @@ func defaultPageHandler(params operations.DefaultPageParams) middleware.Responde http.Redirect(w, params.HTTPRequest, "/index.html", http.StatusFound) }) } + +/*Handle get deweb public infos*/ +type dewebInfo struct { + miscInfo interface{} +} + +func NewDewebInfo(miscInfo interface{}) operations.GetDeWebInfoHandler { + return &dewebInfo{miscInfo: miscInfo} +} + +func (dI *dewebInfo) Handle(params operations.GetDeWebInfoParams) middleware.Responder { + return middleware.ResponderFunc(func(w http.ResponseWriter, runtime runtime.Producer) { + // Add CORS headers + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type") + operations.NewGetDeWebInfoOK().WithPayload(&models.DeWebInfo{ + App: "deweb", + Version: config.Version, + Misc: dI.miscInfo, + }).WriteResponse(w, runtime) + }) +} diff --git a/server/int/api/middlewares.go b/server/int/api/middlewares.go index 957165b6..a7dd8706 100644 --- a/server/int/api/middlewares.go +++ b/server/int/api/middlewares.go @@ -24,6 +24,8 @@ var notAvailableZip []byte //go:embed resources/brokenWebsite.zip var brokenWebsiteZip []byte +var dewebInfoPath = "/__deweb_info" + // SubdomainMiddleware handles subdomain website serving. func SubdomainMiddleware(handler http.Handler, conf *config.ServerConfig) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -37,6 +39,14 @@ func SubdomainMiddleware(handler http.Handler, conf *config.ServerConfig) http.H return } + // __deweb_info endpoint must be available from all subdomains + if r.URL.Path == dewebInfoPath { + logger.Debug("SubdomainMiddleware: Requested __deweb_info endpoint. Preceding with the next handler.") + handler.ServeHTTP(w, r) + + return + } + path := cleanPath(r.URL.Path) logger.Debugf("SubdomainMiddleware: Subdomain %s found, resolving address", subdomain)