diff --git a/Dockerfile b/Dockerfile index aac1ca5..883246c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,20 +26,15 @@ WORKDIR /data COPY --from=builder /app/main /app/main ARG UNCONDITIONAL_API_SOURCE_REPO -ARG UNCONDITIONAL_API_SOURCE_CLIENT_KEY -ARG UNCONDITIONAL_API_FEED_REPO_HOST -ARG UNCONDITIONAL_API_FEED_REPO_KEY +ARG UNCONDITIONAL_API_FEED_REPO_INDEX ARG UNCONDITIONAL_API_LOG_ENV ENV UNCONDITIONAL_API_SOURCE_REPO=${UNCONDITIONAL_API_SOURCE_REPO} -ENV UNCONDITIONAL_API_SOURCE_CLIENT_KEY=${UNCONDITIONAL_API_SOURCE_CLIENT_KEY} -ENV UNCONDITIONAL_API_FEED_REPO_HOST=${UNCONDITIONAL_API_FEED_REPO_HOST} -ENV UNCONDITIONAL_API_FEED_REPO_KEY=${UNCONDITIONAL_API_FEED_REPO_KEY} +ENV UNCONDITIONAL_API_FEED_REPO_INDEX=${UNCONDITIONAL_API_FEED_REPO_INDEX} ENV UNCONDITIONAL_API_LOG_ENV=${UNCONDITIONAL_API_LOG_ENV} RUN --mount=type=secret,id=UNCONDITIONAL_API_SOURCE_CLIENT_KEY \ --mount=type=secret,id=UNCONDITIONAL_API_FEED_REPO_HOST \ - --mount=type=secret,id=UNCONDITIONAL_API_FEED_REPO_INDEX \ --mount=type=secret,id=UNCONDITIONAL_API_FEED_REPO_KEY \ UNCONDITIONAL_API_SOURCE_CLIENT_KEY="$(cat /run/secrets/UNCONDITIONAL_API_SOURCE_CLIENT_KEY)" \ UNCONDITIONAL_API_FEED_REPO_HOST="$(cat /run/secrets/UNCONDITIONAL_API_FEED_REPO_HOST)" \ @@ -55,18 +50,14 @@ ARG UNCONDITIONAL_API_ADDRESS ARG UNCONDITIONAL_API_ALLOWED_ORIGINS ARG UNCONDITIONAL_API_PORT ARG UNCONDITIONAL_API_SOURCE_REPO -ARG UNCONDITIONAL_API_SOURCE_CLIENT_KEY ARG UNCONDITIONAL_API_LOG_ENV -ARG UNCONDITIONAL_API_FEED_REPO_HOST -ARG UNCONDITIONAL_API_FEED_REPO_KEY +ARG UNCONDITIONAL_API_FEED_REPO_INDEX ENV UNCONDITIONAL_API_ADDRESS=${UNCONDITIONAL_API_ADDRESS} ENV UNCONDITIONAL_API_ALLOWED_ORIGINS=${UNCONDITIONAL_API_ALLOWED_ORIGINS} ENV UNCONDITIONAL_API_PORT=${UNCONDITIONAL_API_PORT} ENV UNCONDITIONAL_API_SOURCE_REPO=${UNCONDITIONAL_API_SOURCE_REPO} -ENV UNCONDITIONAL_API_SOURCE_CLIENT_KEY=${UNCONDITIONAL_API_SOURCE_CLIENT_KEY} ENV UNCONDITIONAL_API_LOG_ENV=${UNCONDITIONAL_API_LOG_ENV} -ENV UNCONDITIONAL_API_FEED_REPO_HOST=${UNCONDITIONAL_API_FEED_REPO_HOST} -ENV UNCONDITIONAL_API_FEED_REPO_KEY=${UNCONDITIONAL_API_FEED_REPO_KEY} +ENV UNCONDITIONAL_API_FEED_REPO_INDEX=${UNCONDITIONAL_API_FEED_REPO_INDEX} ENTRYPOINT ["./app/main","serve", "--address", "0.0.0.0", "--port","8080","--feed-repo-index","feeds"] diff --git a/Makefile b/Makefile index cb0d66d..0aa0799 100644 --- a/Makefile +++ b/Makefile @@ -56,6 +56,11 @@ test-integration: build: @go build --tags=release -o ${_PROJECT_DIRECTORY}/bin/unconditional-server +.PHONY: deploy + +deploy: + @sh ./scripts/deploy.sh + # Helpers check-variable-%: # detection of undefined variables. @[[ "${${*}}" ]] || (echo '*** Please define variable `${*}` ***' && exit 1) diff --git a/api/api.gen.go b/api/api.gen.go index 9b4e2c4..61ab4e3 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -69,6 +69,11 @@ type SourceReleaseVersion struct { Version string `json:"version"` } +// GetV1SearchFeedQueryParams defines parameters for GetV1SearchFeedQuery. +type GetV1SearchFeedQueryParams struct { + BySimilarity *bool `json:"bySimilarity,omitempty"` +} + // GetV1VersionJSONBody defines parameters for GetV1Version. type GetV1VersionJSONBody map[string]interface{} @@ -82,7 +87,7 @@ type ServerInterface interface { GetV1SearchContextQuery(ctx echo.Context, query string) error // (GET /v1/search/feed/{query}) - GetV1SearchFeedQuery(ctx echo.Context, query string) error + GetV1SearchFeedQuery(ctx echo.Context, query string, params GetV1SearchFeedQueryParams) error // Your GET endpoint // (GET /v1/version) GetV1Version(ctx echo.Context) error @@ -120,8 +125,17 @@ func (w *ServerInterfaceWrapper) GetV1SearchFeedQuery(ctx echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter query: %s", err)) } + // Parameter object where we will unmarshal all parameters from the context + var params GetV1SearchFeedQueryParams + // ------------- Optional query parameter "bySimilarity" ------------- + + err = runtime.BindQueryParameter("form", true, false, "bySimilarity", ctx.QueryParams(), ¶ms.BySimilarity) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter bySimilarity: %s", err)) + } + // Invoke the callback with all the unmarshalled arguments - err = w.Handler.GetV1SearchFeedQuery(ctx, query) + err = w.Handler.GetV1SearchFeedQuery(ctx, query, params) return err } @@ -171,22 +185,22 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/9SW246jRhPHX2XU33fJGnwYH7jLJpvVKBdRDrtStJqLhi6gPfTB3QVjPOLdowbG4DGe", - "9aw2UnJliy6q/vWrA/1EYiW0kiDRkvCJ2DgDQZu/H4xRxv3RRmkwyKF5HCsG7hcrDSQkXCKkYEjtEQHW", - "0nR4aNFwmZK69oiBXcENMBJ+ORp6rbN779leRVuIkXhk/86i0jlPM3TuOCMhKdKt2WblAxMgZ43PnwHY", - "nehCnqpEjvmYEo8UJv+6QmfkdU6ulBcLUUW4o6La8aqXhyDO1TGKjbhEGUGRhM2Dd8iFY3KmmD+n+H8D", - "CQnJ//y+Zn5XML9nUXskpzItxkvhkZzLh9EDqwoTj79jCyGoqUbPLqF+gbQ16111Qo5hB6q9FtCV4Pd0", - "sRfRMtJpQZMm7B9ATZz9qCTCHn8CpDy350X4RkivkcgKEUnK8+/B6ZnOEVfvfYDq/uh3POvrEM4ioCs2", - "Xa6XyWreITQlmPcFz9lnMJYrObYJhOB4ntF4DFD8YbZZRgFj2MYoe8fXONgWenp72NLyIZ1TUr/k1qnp", - "3Z6gOcvmOjB6FVUHinZp5wccgLnIJHIxvjarI3JOxu/Vdxur3yEHauH49gsWx5Fq5ZyReBuE2WKnponY", - "zDdRettCGBMxMmAWP2k3yuyHq9tktswSvk5xu4kY/5Y2weVer6CcS23Y9rxNymPqp/KGjMayu/Ibldg4", - "f1yUxXynHSoXnctEDT5J5JOMlWQcuZI0H/RrSKaTwDWC0iCp5iQk80kwmRGPaIpZg9Qvp75t5tyP20H3", - "n3YFmKp2pyk0SlwNqHN/5yR9BPw8PdkNv7kXGq+GCkAwloRfngh3Elwk4hFJhZO66yx7gGgK8Lpbwtgu", - "u3fGVitp2x6YBUG7KiSCbNRRrXMeN/r8rW3r2vt7fWxGFlyDmIGNDdfYYvz1F0fx9jtGbq9CI6HuJIKR", - "NL9p5+rm2XC8OzhTsN6ZdQDRnrXuBgVNANhbqum++P/mUnIEYa+6trg7Un0cMGoMrf5TdX1UNp8lbLHP", - "MjOo62BxXa5lv19cZcDie8WqNyV4upfqbuP9YzM4/H5cKNLwmkT+UoW5+fjhzxuQTCsuL63O3SIvD/FB", - "p3LzmHVp2CZa29PNvZ1kiNqGvk81nxTDRTphtPLLKam9oWno+7mKaZ4pi+E6WAeD8+lsNQkmwWTaHdyP", - "61osHjdFsAhuo9s5kLr+OwAA//+q0t+IMg0AAA==", + "H4sIAAAAAAAC/9SXzW7jNhDHXyVge9RasqPEsW7ddrsIeija7S5QLHKgpJHEiF8mR3KcQO9eUJItOZaz", + "ziJ76CmGOJz58zcfZJ5IooRWEiRaEj0RmxQgaPvzgzHKuB/aKA0GGbSfE5WC+4tbDSQiTCLkYEjjEQHW", + "0ny8aNEwmZOm8YiBdcUMpCT6ujf0Omd33s5exfeQIPHIwzuLSnOWF+jcsZRERGSZvTdlfDNXeNX6/B0g", + "vRV9yEOVyJBPKfFIZfi3FTojr3dypjzK461MCobJ+rEa5CGIY3UpxVZcpoygSKL2wztkwjE5Usx2R/zZ", + "QEYi8pM/5MzvE+YPLBqPcCrzajoVHuFMlpMLVlUmmd5jKyGo2U6unUL9DGlnNrjqhezDjlR7HaAzwZcb", + "2GQiCx/y9SW0YT8BNUnxq5IID/gbIGXcHifhOyG9RKKoRCwp42/BaUdnj2vwPkJ1t/c7ferzEGZFZjiF", + "uFxRW/YITQ3mfcV4+gWMZUpOTQIhGB6faDpGeIP8cllXmypc6jZGPTg+x4HYrB9MfoVBXVskzXNuvZrB", + "7QGao9Oc2dTXeVjbe6uu1eNmBOYkk9jF+FavTsg5aL8X97ZWfwMHamG/+xmLfUt1co5IvA5CKEKZJkwo", + "MEncQZgSMdFgFj9r18rpL2eXiXrM+TZYas4fN/H3lEkeLkotsqsVrGx8XCb1/uiH8saMpk53Hqq1kWyV", + "UFiurbl2wd34lpkaXUnks0yUTBkyJSkf1WtE5rPAFYLSIKlmJCKXs2C2IB7RFIsWqV/Pfdv2uZ90je4/", + "rSsw28at5tAqcTmgzv2tk/QR8Mv8YDb85Ta0Xg0VgGAsib4+EeYkuEjEI5IKJ3XdWw4A0VTg9a+EqVl2", + "54ytVtJ2NbAIgm5USATZqqNac5a0+vx72+V18Pdy20wMuBZxCjYxTGOH8c8/HMWrN4zcPYUmQt1KBCMp", + "v+j66mJnOF0dy+RyFdbXpbnCLOvcjRKaAaSvyaa78V9K5S55fS7j7ScmGKeGoft6lMJYKQ5UvkEOGYKw", + "Z71X3OOo2XcWNYZu/1cJhesKZFEuxGJedk/SH9JTfZ2MBuHp2hjmlQsAFt+rdPsqbodzrukn6A/r6fF9", + "dCL342cX+VdV5uLjh38uQKZaMXlqFKsqAR3WqypeLx77Y9g2Wpea9v8AUiBqG/k+1WxWjQfzLKVbv56T", + "xhubRr7PVUJ5oSxGN8FNMFqfL5azYBbM5v3C3Yn7KSjFKriXsQpZQJrmvwAAAP//A98fT4INAAA=", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/swagger.yaml b/api/swagger.yaml index d4de152..aeb301f 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -1,57 +1,62 @@ openapi: 3.0.2 x-stoplight: - id: 44w9u0405b53e + id: g0km90jnbo4i0 info: title: Unconditional - version: "1.0" + version: '1.0' servers: - - url: "https://api.unconditional.day/v1" - - url: "http://localhost:8080" - - url: "127.0.0.1:8080" + - url: 'https://api.unconditional.day/v1' + - url: 'http://localhost:8080' + - url: '127.0.0.1:8080' paths: - "/v1/search/feed/{query}": + '/v1/search/feed/{query}': get: responses: - "200": + '200': description: OK content: application/json: schema: type: array items: - $ref: "#/components/schemas/FeedItem" - "500": + $ref: '#/components/schemas/FeedItem' + '500': description: Internal Server Error content: application/json: schema: type: object - $ref: "#/components/schemas/Error" - parameters: - - name: query - in: path - required: true - schema: - type: string + $ref: '#/components/schemas/Error' x-stoplight: - id: wosl2fd4xhhrd - "/v1/search/context/{query}": + id: e6uenhk2m21k5 + parameters: + - schema: + type: boolean + in: query + name: bySimilarity + parameters: + - schema: + type: string + name: query + in: path + required: true + '/v1/search/context/{query}': get: responses: - "200": + '200': description: OK content: application/json: schema: type: object - $ref: "#/components/schemas/SearchContextDetails" - "500": + $ref: '#/components/schemas/SearchContextDetails' + '500': description: Internal Server Error content: application/json: schema: type: object - $ref: "#/components/schemas/Error" + $ref: '#/components/schemas/Error' parameters: - name: query in: path @@ -59,22 +64,22 @@ paths: schema: type: string x-stoplight: - id: idoe8qr80ebxd - "/v1/version": + id: 7c394v6kr5tff + /v1/version: get: summary: Your GET endpoint tags: [] responses: - "200": + '200': description: OK content: application/json: schema: type: object - $ref: "#/components/schemas/ServerVersion" + $ref: '#/components/schemas/ServerVersion' operationId: get-v1-version x-stoplight: - id: q4lvzczpgn9wh + id: oucep4v9ubq2z requestBody: content: application/json: @@ -98,7 +103,7 @@ components: type: string image: type: object - $ref: "#/components/schemas/FeedImage" + $ref: '#/components/schemas/FeedImage' date: type: string format: date-time @@ -110,7 +115,7 @@ components: - language - date x-stoplight: - id: xa4xmb6bpguaf + id: kwewfmf4xgq3e FeedImage: type: object properties: @@ -122,11 +127,11 @@ components: - url - title x-stoplight: - id: cmmybtqamyqiy + id: albynchitcqzu SearchContextDetails: type: object x-stoplight: - id: 2bea7d1686f73 + id: fhfrlaebk9ask properties: title: type: string @@ -156,51 +161,51 @@ components: - message - code x-stoplight: - id: ugjrjhvkdmen2 + id: mffsjrkb81ot5 ServerVersion: title: ServerVersion x-stoplight: - id: 24qo1fm939bg5 + id: 4m4ndcimoercb type: object properties: source: - $ref: "#/components/schemas/SourceReleaseVersion" + $ref: '#/components/schemas/SourceReleaseVersion' build: - $ref: "#/components/schemas/ServerBuildVersion" + $ref: '#/components/schemas/ServerBuildVersion' required: - source - build SourceReleaseVersion: title: SourceReleaseVersion x-stoplight: - id: ufsclw4vu3qp5 + id: qrni9cae7qsr6 type: object properties: version: type: string x-stoplight: - id: t6xp7ev3nprdj + id: g42kpmf59e9sb lastUpdatedAt: type: string x-stoplight: - id: 26hfi8gtj9bdi + id: ozgly07pllzwb required: - version - lastUpdatedAt ServerBuildVersion: title: ServerBuildVersion x-stoplight: - id: p7byzats6s3zt + id: a6g4vsjso6ozw type: object properties: commit: type: string x-stoplight: - id: eoik296b0ddt3 + id: 48tl37vuwu47p version: type: string x-stoplight: - id: jup15zjavkg3a + id: mwqxrg5t0vvst required: - commit - version diff --git a/internal/app/feed.go b/internal/app/feed.go index 8ec1116..3a32757 100644 --- a/internal/app/feed.go +++ b/internal/app/feed.go @@ -7,6 +7,8 @@ import ( type FeedRepository interface { // Search returns the results of a search query. Find(query string) ([]Feed, error) + // Search returns the results of a search query by similarity. + FindBySimilarity(query string) ([]Feed, error) // Index indexes a document. Save(doc Feed) error // Update a document in index. diff --git a/internal/repository/typesense/feed.go b/internal/repository/typesense/feed.go index 533722e..fdec117 100644 --- a/internal/repository/typesense/feed.go +++ b/internal/repository/typesense/feed.go @@ -55,6 +55,42 @@ func (f *FeedRepository) Find(query string) ([]app.Feed, error) { return feeds, nil } + +func (f *FeedRepository) FindBySimilarity(query string) ([]app.Feed, error){ + searchParameters := &api.SearchCollectionParams{ + Q: query, + QueryBy: "title_summary_embedding", + } + searchResult, err := f.client.Collection("feeds").Documents().Search(f.ctx, searchParameters) + if err != nil { + return nil, err + } + + feeds := make([]app.Feed, len(*searchResult.Hits)) + for i, x := range *searchResult.Hits { + doc := *x.Document + + date, err := time.Parse(time.RFC3339, doc["date"].(string)) + if err != nil { + return nil, err + } + + f := app.Feed{ + Title: doc["title"].(string), + Link: doc["link"].(string), + Source: doc["source"].(string), + Language: doc["language"].(string), + Summary: doc["summary"].(string), + Date: date, + } + + feeds[i] = f + } + + return feeds, nil +} + + func (f *FeedRepository) Save(doc app.Feed) error { docMap := map[string]interface{}{ "id": doc.Link, diff --git a/internal/webserver/server.go b/internal/webserver/server.go index d5dc853..c58362c 100644 --- a/internal/webserver/server.go +++ b/internal/webserver/server.go @@ -67,8 +67,15 @@ func (s *Server) Start() error { } // (GET /v1/search/feed/{query}) -func (s *Server) GetV1SearchFeedQuery(ctx echo.Context, query string) error { - feeds, err := s.feedRepo.Find(query) +func (s *Server) GetV1SearchFeedQuery(ctx echo.Context, query string, params api.GetV1SearchFeedQueryParams) error { + var feeds []app.Feed + var err error + + if params.BySimilarity != nil && *params.BySimilarity { + feeds, err = s.feedRepo.FindBySimilarity(query) + } else { + feeds, err = s.feedRepo.Find(query) + } if err != nil { e := api.Error{ Code: http.StatusInternalServerError, diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 49da840..f17668e 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -12,6 +12,6 @@ flyctl deploy --remote-only \ --build-secret UNCONDITIONAL_API_SOURCE_CLIENT_KEY="$UNCONDITIONAL_API_SOURCE_CLIENT_KEY" \ --build-secret UNCONDITIONAL_API_FEED_REPO_KEY="$UNCONDITIONAL_API_FEED_REPO_KEY" \ --build-secret UNCONDITIONAL_API_FEED_REPO_HOST="$UNCONDITIONAL_API_FEED_REPO_HOST" \ - --build-secret UNCONDITIONAL_API_FEED_REPO_INDEX="$UNCONDITIONAL_API_FEED_REPO_INDEX" \ + --build-arg UNCONDITIONAL_API_FEED_REPO_INDEX="$UNCONDITIONAL_API_FEED_REPO_INDEX" \ --build-arg UNCONDITIONAL_API_BUILD_COMMIT_VERSION="$UNCONDITIONAL_API_BUILD_COMMIT_VERSION" \ --build-arg UNCONDITIONAL_API_BUILD_RELEASE_VERSION="$UNCONDITIONAL_API_BUILD_RELEASE_VERSION"