diff --git a/controllers/rest/fixtures/blog-with-basepath.yaml b/controllers/rest/fixtures/blog-with-basepath.yaml new file mode 100644 index 00000000..206a1f5d --- /dev/null +++ b/controllers/rest/fixtures/blog-with-basepath.yaml @@ -0,0 +1,877 @@ +openapi: 3.0.3 +info: + title: Blog + description: Blog example + version: 1.0.0 +servers: + - url: https://prod1.weos.sh/blog/dev + description: WeOS Dev + - url: https://prod1.weos.sh/blog/v1 +x-weos-config: + basePath: /base/path + database: + driver: sqlite3 + database: test.db + databases: + - title: default + driver: sqlite3 + database: test.db + rest: + middleware: + - RequestID + - Recover + - ZapLogger +components: + schemas: + Category: + type: object + properties: + title: + type: string + description: + type: string + required: + - title + x-identifier: + - title + Author: + type: object + properties: + id: + type: string + format: ksuid + firstName: + type: string + lastName: + type: string + email: + type: string + format: email + required: + - firstName + - lastName + x-identifier: + - id + - email + Blog: + type: object + properties: + url: + type: string + format: uri + x-unique: true + title: + type: string + description: + type: string + nullable: true + author: + $ref: "#/components/schemas/Author" + cost: + type: number + format: double + nullable: true + status: + type: string + nullable: true + enum: + - "null" + - unpublished + - published + image: + type: string + format: byte + nullable: true + categories: + type: array + nullable: true + items: + $ref: "#/components/schemas/Category" + posts: + type: array + nullable: true + items: + $ref: "#/components/schemas/Post" + lastUpdated: + type: string + format: date-time + nullable: true + x-update: + - Add Blog + - Update Blog + created: + type: string + format: date-time + nullable: true + x-update: + - Add Blog + required: + - title + - url + Post: + type: object + properties: + title: + type: string + description: + type: string + nullable: true + author: + $ref: "#/components/schemas/Author" + created: + type: string + format: date-time +paths: + /: + get: + operationId: Homepage + responses: + 201: + description: Application Homepage + content: + text/html: + example: | + + + Health + + + This is a landing page + + + /page: + get: + summary: Test + parameters: + - in: header + name: Accept + schema: + type: string + responses: + '200': + description: Test Page + content: + text/html: + example: | + + + Test Page + + + This is a test page + + + schema: + type: string + application/json: + example: + blog: + value: { + "title": "API Testing", + "url": "www.example.com", + "email": "testing@example.com" + } + /another: + get: + operationId: Homepage + parameters: + - in: header + name: Accept + schema: + type: string + responses: + 201: + description: Application Homepage + content: + text/html: + + application/json: + example: + blog: + value: { + "title": "API Testing", + "url": "www.example.com", + "email": "testing@example.com" + } + /badtemplates: + get: + operationId: getAsset + responses: + 200: + description: Homepage + x-templates: + - ./fixtures/templates/base12.html + 404: + description: File not found + 402: + description: User not authenticated + /badtemplates1: + get: + operationId: getAsset + responses: + 200: + description: Homepage + x-templates: + - ./fixtures/templates/badTem.html + 404: + description: File not found + 402: + description: User not authenticated + /multipletemplates: + get: + operationId: getAsset + responses: + 200: + description: Homepage + x-templates: + - ./fixtures/templates/index.html + - ./fixtures/templates/ibase.html + 404: + description: File not found + 402: + description: User not authenticated + /templates: + get: + operationId: getAsset + parameters: + - in: query + name: title + schema: + type: string + - in: query + name: content + schema: + type: string + responses: + 200: + description: Homepage + x-templates: + - ./fixtures/templates/base1.html + 404: + description: File not found + 402: + description: User not authenticated + /health: + summary: Health Check + get: + x-controller: HealthCheck + x-middleware: + - Recover + - ZapLogger + responses: + 200: + description: Health Response + 500: + description: API Internal Error + /api: + get: + operationId: Get API Details + x-controller: APIDiscovery + responses: + 200: + description: API Details + content: + application/json: + schema: + type: string + /blogs: + parameters: + - in: header + name: someHeader + schema: + type: string + - in: header + name: someOtherHeader + schema: + type: string + x-context-name: soh + - in: header + name: X-Account-Id + schema: + type: string + x-context-name: AccountID + - in: query + name: q + schema: + type: string + - in: query + name: cost + schema: + type: number + - in: query + name: leverage + schema: + type: number + format: double + post: + operationId: Add Blog + summary: Create Blog + x-projection: Default + x-event-dispatcher: Default + x-command-disptacher: Default + requestBody: + description: Blog info that is submitted + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Blog" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/Blog" + multipart/form-data: + schema: + $ref: "#/components/schemas/Blog" + responses: + 201: + description: Add Blog to Aggregator + content: + application/json: + schema: + $ref: "#/components/schemas/Blog" + get: + operationId: Get Blogs + summary: Get List of Blogs + parameters: + - in: query + name: page + schema: + type: integer + - in: query + name: l + x-alias: limit + schema: + type: integer + - in: query + name: _filters + style: deepObject + explode: true + schema: + type: object + properties: + field: + type: string + operator: + type: string + values: + type: array + items: + type: string + + required: false + description: query string + x-context: + _filters: + - field: status + operator: eq + value: Active + - field: lastUpdated + operator: between + values: + - 2021-12-17 15:46:00 + - 2021-12-18 15:46:00 + - field: categories + operator: in + values: + - Technology + - Javascript + _sorts: + - field: title + order: asc + page: 1 + limit: 10 + responses: + 200: + description: List of blogs + content: + application/json: + schema: + type: object + properties: + total: + type: integer + page: + type: integer + blogs: + type: array + x-alias: items + items: + $ref: "#/components/schemas/Blog" + put: + operationId: Import blogs + requestBody: + content: + text/csv: + schema: + type: string + format: binary + responses: + 201: + description: items created + + /blogs/{id}: + parameters: + - in: query + name: sequence_no + schema: + type: integer + - in: query + name: use_entity_id + schema: + type: boolean + get: + parameters: + - in: path + name: id + schema: + type: string + required: true + description: blog id + - in: header + name: If-Match + schema: + type: string + required: false + - in: query + name: cost + schema: + type: number + - in: query + name: leverage + schema: + type: number + format: double + x-context: + id: 2 + summary: Get Blog by id + operationId: Get Blog + responses: + 200: + description: Blog details without any supporting collections + content: + application/json: + schema: + $ref: "#/components/schemas/Blog" + put: + parameters: + - in: path + name: id + schema: + type: string + required: true + description: blog id + - in: header + name: If-Match + schema: + type: string + - in: query + name: cost + schema: + type: number + - in: query + name: leverage + schema: + type: number + format: double + summary: Update blog details + operationId: Update Blog + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Blog" + responses: + 200: + description: Update Blog + content: + application/json: + schema: + $ref: "#/components/schemas/Blog" + delete: + parameters: + - in: path + name: id + schema: + type: string + required: true + description: blog id + - in: header + name: If-Match + schema: + type: string + x-schema: "Blog" + summary: Delete blog + operationId: Delete Blog + responses: + 200: + description: Blog Deleted + + /posts/: + post: + operationId: Create Blog Post + summary: Create Blog Post + requestBody: + description: Post details + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/Post" + responses: + 201: + description: Post + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + put: + operationId: Import Blog Posts + summary: Import Blog Posts + requestBody: + description: List of posts to import + required: true + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/Post" + application/x-www-form-urlencoded: + schema: + type: array + items: + $ref: "#/components/schemas/Post" + responses: + 201: + description: Post + get: + operationId: Get Posts + summary: Get a blog's list of posts + parameters: + - in: query + name: page + schema: + type: integer + - in: query + name: l + x-alias: limit + schema: + type: integer + - in: query + name: _filters + style: deepObject + explode: true + schema: + type: object + properties: + field: + type: string + operator: + type: string + values: + type: array + items: + type: string + + required: false + description: query string + responses: + 200: + description: List of blog posts + content: + application/json: + schema: + type: object + properties: + total: + type: integer + page: + type: integer + items: + type: array + items: + $ref: "#/components/schemas/Post" + + /posts/{id}: + get: + parameters: + - in: path + name: id + schema: + type: string + required: true + summary: Get blog post by id + responses: + 200: + description: Get blog post information + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + put: + parameters: + - in: path + name: id + schema: + type: string + required: true + summary: Update post + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + responses: + 200: + description: Get blog post information + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + delete: + parameters: + - in: path + name: id + schema: + type: string + required: true + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Post" + summary: Delete post + responses: + 200: + description: Delete post + + + /categories/: + post: + operationId: Create Blog Category + summary: Create Blog Category + requestBody: + description: Post details + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Category" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/Category" + responses: + 201: + description: Post + content: + application/json: + schema: + $ref: "#/components/schemas/Category" + get: + operationId: Get Categories + summary: Get a blog's list of categories + parameters: + - in: query + name: q + schema: + type: string + required: false + description: query string + responses: + 200: + description: List of blog categories + content: + application/json: + schema: + type: object + properties: + total: + type: integer + page: + type: integer + items: + type: array + items: + $ref: "#/components/schemas/Category" + + /categories/{title}: + get: + parameters: + - in: path + name: title + schema: + type: string + required: true + summary: Get blog category by title + responses: + 200: + description: Get blog category information + content: + application/json: + schema: + $ref: "#/components/schemas/Category" + put: + parameters: + - in: path + name: title + schema: + type: string + required: true + summary: Update category + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Category" + responses: + 200: + description: Get blog category information + content: + application/json: + schema: + $ref: "#/components/schemas/Category" + delete: + parameters: + - in: path + name: title + schema: + type: string + required: true + requestBody: + required: false + content: + application/json: + schema: + $ref: "#/components/schemas/Category" + summary: Delete category + responses: + 200: + description: Delete category + + /authors/: + post: + operationId: Create Blog Author + summary: Create Blog Author + requestBody: + description: Author details + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Author" + application/x-www-form-urlencoded: + schema: + $ref: "#/components/schemas/Author" + responses: + 201: + description: Post + content: + application/json: + schema: + $ref: "#/components/schemas/Author" + get: + operationId: Get Authors + summary: Get a blog's list of authors + parameters: + - in: query + name: q + schema: + type: string + required: false + description: query string + responses: + 200: + description: List of blog authors + content: + application/json: + schema: + type: object + properties: + total: + type: integer + page: + type: integer + items: + type: array + items: + $ref: "#/components/schemas/Author" + + /authors/{id}: + get: + parameters: + - in: path + name: id + schema: + type: string + required: true + - in: header + name: email + schema: + type: string + format: email + required: true + summary: Get Author by email and id + responses: + 200: + description: Get author information + content: + application/json: + schema: + $ref: "#/components/schemas/Author" + put: + parameters: + - in: path + name: id + schema: + type: string + required: true + - in: header + name: email + schema: + type: string + format: email + required: true + summary: Update Author details + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/Author" + responses: + 200: + description: Author details + content: + application/json: + schema: + $ref: "#/components/schemas/Author" + delete: + parameters: + - in: path + name: id + schema: + type: string + required: true + - in: header + name: email + schema: + type: string + format: email + required: true + requestBody: + required: false + content: + application/json: + schema: + $ref: "#/components/schemas/Author" + summary: Delete author + responses: + 200: + description: Delete author diff --git a/controllers/rest/middleware_context.go b/controllers/rest/middleware_context.go index 08161a45..91921b1f 100644 --- a/controllers/rest/middleware_context.go +++ b/controllers/rest/middleware_context.go @@ -34,6 +34,10 @@ func Context(api Container, projection projections.Projection, commandDispatcher if accountID != "" { cc = context.WithValue(cc, weosContext.ACCOUNT_ID, accountID) } + //set the basePath in context so that urls can be generated correctly #216 + if api.GetWeOSConfig() != nil { + cc = context.WithValue(cc, "BASE_PATH", api.GetWeOSConfig().BasePath) + } //use the path information to get the parameter values contextValues, err := parseParams(c, path.Parameters, entityFactory) //add parameter values to the context diff --git a/controllers/rest/middleware_context_test.go b/controllers/rest/middleware_context_test.go index d46c4aa2..19257cc0 100644 --- a/controllers/rest/middleware_context_test.go +++ b/controllers/rest/middleware_context_test.go @@ -4,6 +4,10 @@ import ( "bytes" "encoding/json" "fmt" + "github.com/getkin/kin-openapi/openapi3" + "github.com/labstack/echo/v4" + "github.com/wepala/weos/context" + "github.com/wepala/weos/controllers/rest" "github.com/wepala/weos/model" "io/ioutil" "mime/multipart" @@ -15,11 +19,6 @@ import ( "strconv" "strings" "testing" - - "github.com/getkin/kin-openapi/openapi3" - "github.com/labstack/echo/v4" - "github.com/wepala/weos/context" - "github.com/wepala/weos/controllers/rest" ) func TestContext(t *testing.T) { @@ -71,6 +70,29 @@ func TestContext(t *testing.T) { e.ServeHTTP(resp, req) }) + t.Run("check that basePath is added by default", func(t *testing.T) { + api, err := rest.New("./fixtures/blog-with-basepath.yaml") + if err != nil { + t.Fatalf("error loading api specification '%s'", err) + } + + path := api.GetConfig().Paths.Find("/blogs") + mw := rest.Context(api, nil, nil, nil, entityFactory, path, path.Get) + handler := mw(func(ctxt echo.Context) error { + //check that certain parameters are in the context + cc := ctxt.Request().Context() + basePath := cc.Value("BASE_PATH").(string) + if basePath != "/base/path" { + t.Errorf("expected the base path to be '/base/path', got '%s'", basePath) + } + return nil + }) + resp := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, "/blogs", nil) + e.GET("/blogs", handler) + e.ServeHTTP(resp, req) + }) + t.Run("parameter in the header should be added to context", func(t *testing.T) { paramName := "someHeader" paramValue := "123"