{"id":19330534,"url":"https://github.com/aventhis/go-bootcamp-elasticsearch-recommender","last_synced_at":"2025-07-01T02:33:23.053Z","repository":{"id":258207576,"uuid":"864970576","full_name":"aventhis/go-bootcamp-elasticsearch-recommender","owner":"aventhis","description":"Go Bootcamp - Elasticsearch Recommender: Создание рекомендательного сервиса с использованием Elasticsearch, HTML-интерфейса и аутентификации JWT на Go.","archived":false,"fork":false,"pushed_at":"2024-10-17T16:34:04.000Z","size":561,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-24T06:47:42.914Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/aventhis.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-29T16:46:55.000Z","updated_at":"2024-10-17T16:34:08.000Z","dependencies_parsed_at":null,"dependency_job_id":"54d9ec43-9e51-4519-bac0-43a333543ae7","html_url":"https://github.com/aventhis/go-bootcamp-elasticsearch-recommender","commit_stats":null,"previous_names":["aventhis/go-bootcamp-elasticsearch-recommender"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/aventhis/go-bootcamp-elasticsearch-recommender","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aventhis%2Fgo-bootcamp-elasticsearch-recommender","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aventhis%2Fgo-bootcamp-elasticsearch-recommender/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aventhis%2Fgo-bootcamp-elasticsearch-recommender/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aventhis%2Fgo-bootcamp-elasticsearch-recommender/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/aventhis","download_url":"https://codeload.github.com/aventhis/go-bootcamp-elasticsearch-recommender/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/aventhis%2Fgo-bootcamp-elasticsearch-recommender/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261337130,"owners_count":23143836,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-10T02:36:59.869Z","updated_at":"2025-06-22T17:39:57.231Z","avatar_url":"https://github.com/aventhis.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Day 03 - Go Boot camp\n\n## Tasty Discoveries\n\n💡 [Tap here](https://new.oprosso.net/p/4cb31ec3f47a4596bc758ea1861fb624) **to leave your feedback on the project**. It's anonymous and will help our team make your educational experience better. We recommend completing the survey immediately after the project.\n\n## Contents\n\n1. [Chapter I](#chapter-i) \\\n    1.1. [General rules](#general-rules)\n2. [Chapter II](#chapter-ii) \\\n    2.1. [Rules of the day](#rules-of-the-day)[README.md](../../go-bootcamp/Go_Day03-1/README.md)\n3. [Chapter III](#chapter-iii) \\\n    3.1. [Intro](#intro)\n4. [Chapter IV](#chapter-iv) \\\n    4.1. [Exercise 00: Loading Data](#exercise-00-loading-data)\n5. [Chapter V](#chapter-v) \\\n    5.1. [Exercise 01: Simplest Interface](#exercise-01-simplest-interface)\n6. [Chapter VI](#chapter-vi) \\\n    6.1. [Exercise 02: Proper API](#exercise-02-proper-api)\n7. [Chapter VII](#chapter-vii) \\\n    7.1. [Exercise 03: Closest Restaurants](#exercise-03-closest-restaurants)\n8. [Chapter VIII](#chapter-viii) \\\n    8.1. [Exercise 04: JWT](#exercise-04-jwt)\n\n\n\u003ch2 id=\"chapter-i\" \u003eChapter I\u003c/h2\u003e\n\u003ch2 id=\"general-rules\" \u003eGeneral rules\u003c/h2\u003e\n\n- Your programs should not quit unexpectedly (giving an error on a valid input). If this happens, your project will be considered non functional and will receive a 0 during the evaluation.\n- We encourage you to create test programs for your project even though this work won't have to be submitted and won't be graded. It will give you a chance to easily test your work and your peers' work. You will find those tests especially useful during your defence. Indeed, during defence, you are free to use your tests and/or the tests of the peer you are evaluating.\n- Submit your work to your assigned git repository. Only the work in the git repository will be graded.\n- If your code is using external dependencies, it should use [Go Modules](https://go.dev/blog/using-go-modules) for managing them\n\n\u003ch2 id=\"chapter-ii\" \u003eChapter II\u003c/h2\u003e\n\u003ch2 id=\"rules-of-the-day\" \u003eRules of the day\u003c/h2\u003e\n\n- You should only turn in `*.go` files and (in case of external dependencies) `go.mod` + `go.sum`\n- Your code for this task should be buildable with just `go build`\n- All inputs ('page'/'lat'/'long') should be thouroughly validated and never cause HTTP 500 (only HTTP 400/401 is acceptable, with a meaningful error message, as explained in EX02)\n\n\u003ch2 id=\"chapter-iii\" \u003eChapter III\u003c/h2\u003e\n\u003ch2 id=\"intro\" \u003eIntro\u003c/h2\u003e\n\nPeople tend to love some recommending apps. It helps to avoid thinking too much about what to buy, where to go and what to eat.\n\nAlso, pretty much everyone has a phone with a geolocation. How often did you try finding some restaurants in your area for dinner?\n\nLet's think a bit about how these services work and build one of our own, really simple one, shall we?\n\n\u003ch2 id=\"chapter-iv\" \u003eChapter IV\u003c/h2\u003e\n\u003ch3 id=\"ex00\"\u003eExercise 00: Loading Data\u003c/h3\u003e\n\nThere are lots and lots of various databases on the market. But, because we're trying to provide the ability to search for things, let's use [Elasticsearch](https://www.elastic.co/downloads/elasticsearch). \u003c!---All examples provided have been tested on version 7.9.2.--\u003e\n\nElasticsearch is a full text search engine built on top of [Lucene](https://en.wikipedia.org/wiki/Apache_Lucene). It provides an HTTP API that we will be using in this task.\n\nOur provided dataset of restaurants (taken from an Open Data portal) consists of more than 13 thousands of restaurants in the area of Moscow, Russia (you can put together another similar dataset for any other location you want). Every entry has:\n\n- ID\n- Name\n- Address\n- Phone\n- Longitude\n- Latitude\n\nBefore uploading all entries into the database, let's create an index and a mapping (explicitly specifying data types). Without it Elasticsearch will try to guess field types based on documents provided, and sometimes it won't recognize geopoints.\n\nHere are a couple links to help you get started on things:\n- https://www.elastic.co/guide/en/elasticsearch/reference/8.4/indices-create-index.html\n- https://www.elastic.co/guide/en/elasticsearch/reference/8.4/geo-point.html\n\nStart the database by running `~$ /path/to/elasticsearch/dir/bin/elasticsearch` and let's experiment around.\n\nFor simplicity, let's use \"places\" as a name for an index and \"place\" as a name for an entry. You can create an index using cURL like this:\n\n```\n~$ curl -XPUT \"http://localhost:9200/places\"\n```\n\nbut in this task you should use Go Elasticsearch bindings to do the same thing. Next thing you have to do is to provide type mappings for our data. With cURL it will look like this:\n\n```\n~$ curl -XPUT http://localhost:9200/places/place/_mapping?include_type_name=true -H \"Content-Type: application/json\" -d @\"schema.json\"\n```\n\nwhere `schema.json` looks like this:\n\n```\n{\n  \"properties\": {\n    \"name\": {\n        \"type\":  \"text\"\n    },\n    \"address\": {\n        \"type\":  \"text\"\n    },\n    \"phone\": {\n        \"type\":  \"text\"\n    },\n    \"location\": {\n      \"type\": \"geo_point\"\n    }\n  }\n}\n```\n\nOnce again, provided cURL commands are just a reference for self-testing, this action should be performed by the Go program you write.\n\nNow you have a dataset to upload. You should use [Bulk API](https://www.elastic.co/guide/en/elasticsearch/reference/8.4/docs-bulk.html) to perform that. All existing Elasticsearch bindings provide wrappers for it, for example, [here is a good example](https://github.com/elastic/go-elasticsearch/blob/master/_examples/bulk/indexer.go) for an official client\u003c!--- (keep in mind that you'll need to use client v7 for ES version 7.9, not v8)--\u003e. There are also a couple of third-party clients, choose whichever you prefer.\n\nTo check yourself, you may use cURL. So,\n\n```\n~$ curl -s -XGET \"http://localhost:9200/places\"\n```\n\nshould give you something like this:\n\n```\n{\n  \"places\": {\n    \"aliases\": {},\n    \"mappings\": {\n      \"properties\": {\n        \"address\": {\n          \"type\": \"text\"\n        },\n        \"id\": {\n          \"type\": \"long\"\n        },\n        \"location\": {\n          \"type\": \"geo_point\"\n        },\n        \"name\": {\n          \"type\": \"text\"\n        },\n        \"phone\": {\n          \"type\": \"text\"\n        }\n      }\n    },\n    \"settings\": {\n      \"index\": {\n        \"creation_date\": \"1601810777906\",\n        \"number_of_shards\": \"1\",\n        \"number_of_replicas\": \"1\",\n        \"uuid\": \"4JKa9fgISd6-N130rpNYtQ\",\n        \"version\": {\n          \"created\": \"7090299\"\n        },\n        \"provided_name\": \"places\"\n      }\n    }\n  }\n}\n```\n\nand querying entry by its ID will look like this:\n\n```\n~$ curl -s -XGET \"http://localhost:9200/places/_doc/1\"\n```\n\n```\n{\n  \"_index\": \"places\",\n  \"_type\": \"place\",\n  \"_id\": \"1\",\n  \"_version\": 1,\n  \"_seq_no\": 0,\n  \"_primary_term\": 1,\n  \"found\": true,\n  \"_source\": {\n    \"id\": 1,\n    \"name\": \"SMETANA\",\n    \"address\": \"gorod Moskva, ulitsa Egora Abakumova, dom 9\",\n    \"phone\": \"(499) 183-14-10\",\n    \"location\": {\n      \"lat\": 55.879001531303366,\n      \"lon\": 37.71456500043604\n    }\n  }\n}\n```\n\nPlease note, that the entry with ID=1 may be different from the one in dataset if you decided to use goroutines to speed up the process (that's not a requirement in this task though).\n\n\u003ch2 id=\"chapter-v\" \u003eChapter V\u003c/h2\u003e\n\u003ch3 id=\"ex01\"\u003eExercise 01: Simplest Interface\u003c/h3\u003e\n\nNow let's create an HTML UI for our database. Not much, we just need to render a page with a list of names, addresses and phones so user could see it in a browser.\n\nYou should abstract your database behind an interface. To just return the list of entries and be able to [paginate](https://www.elastic.co/guide/en/elasticsearch/reference/current/paginate-search-results.html) through them, this interface is enough:\n\n```\ntype Store interface {\n    // returns a list of items, a total number of hits and (or) an error in case of one\n    GetPlaces(limit int, offset int) ([]types.Place, int, error)\n}\n```\n\nThere should be no Elasticsearch-related imports in `main` package, as all database-related stuff should rest in `db` package inside your project, and you should only use this interface above to interact with it.\n\nYour HTTP application should run on port 8888, responding with a list of restaurants and providing a simple pagination over it. So. when querying \"http://127.0.0.1:8888/?page=2\" (mind the 'page' GET param) you should be getting a page like this:\n\n```\n\u003c!doctype html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n    \u003cmeta charset=\"utf-8\"\u003e\n    \u003ctitle\u003ePlaces\u003c/title\u003e\n    \u003cmeta name=\"description\" content=\"\"\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\"\u003e\n\u003c/head\u003e\n\n\u003cbody\u003e\n\u003ch5\u003eTotal: 13649\u003c/h5\u003e\n\u003cul\u003e\n    \u003cli\u003e\n        \u003cdiv\u003eSushi Wok\u003c/div\u003e\n        \u003cdiv\u003egorod Moskva, prospekt Andropova, dom 30\u003c/div\u003e\n        \u003cdiv\u003e(499) 754-44-44\u003c/div\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n        \u003cdiv\u003eRyba i mjaso na ugljah\u003c/div\u003e\n        \u003cdiv\u003egorod Moskva, prospekt Andropova, dom 35A\u003c/div\u003e\n        \u003cdiv\u003e(499) 612-82-69\u003c/div\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n        \u003cdiv\u003eHleb nasuschnyj\u003c/div\u003e\n        \u003cdiv\u003egorod Moskva, ulitsa Arbat, dom 6/2\u003c/div\u003e\n        \u003cdiv\u003e(495) 984-91-82\u003c/div\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n        \u003cdiv\u003eTAJJ MAHAL\u003c/div\u003e\n        \u003cdiv\u003egorod Moskva, ulitsa Arbat, dom 6/2\u003c/div\u003e\n        \u003cdiv\u003e(495) 107-91-06\u003c/div\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n        \u003cdiv\u003eBalalaechnaja\u003c/div\u003e\n        \u003cdiv\u003egorod Moskva, ulitsa Arbat, dom 23, stroenie 1\u003c/div\u003e\n        \u003cdiv\u003e(905) 752-88-62\u003c/div\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n        \u003cdiv\u003eIL Pizzaiolo\u003c/div\u003e\n        \u003cdiv\u003egorod Moskva, ulitsa Arbat, dom 31\u003c/div\u003e\n        \u003cdiv\u003e(495) 933-28-34\u003c/div\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n        \u003cdiv\u003eBufet pri Astrahanskih banjah\u003c/div\u003e\n        \u003cdiv\u003egorod Moskva, Astrahanskij pereulok, dom 5/9\u003c/div\u003e\n        \u003cdiv\u003e(495) 344-11-68\u003c/div\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n        \u003cdiv\u003eMU-MU\u003c/div\u003e\n        \u003cdiv\u003egorod Moskva, Baumanskaja ulitsa, dom 35/1\u003c/div\u003e\n        \u003cdiv\u003e(499) 261-33-58\u003c/div\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n        \u003cdiv\u003eBek tu Blek\u003c/div\u003e\n        \u003cdiv\u003egorod Moskva, Tatarskaja ulitsa, dom 14\u003c/div\u003e\n        \u003cdiv\u003e(495) 916-90-55\u003c/div\u003e\n    \u003c/li\u003e\n    \u003cli\u003e\n        \u003cdiv\u003eGlav Pirog\u003c/div\u003e\n        \u003cdiv\u003egorod Moskva, Begovaja ulitsa, dom 17, korpus 1\u003c/div\u003e\n        \u003cdiv\u003e(926) 554-54-08\u003c/div\u003e\n    \u003c/li\u003e\n\u003c/ul\u003e\n\u003ca href=\"/?page=1\"\u003ePrevious\u003c/a\u003e\n\u003ca href=\"/?page=3\"\u003eNext\u003c/a\u003e\n\u003ca href=\"/?page=1364\"\u003eLast\u003c/a\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nA \"Previous\" link should disappear on page 1 and \"Next\" link should disappear on last page.\n\nIMPORTANT NOTE: You may notice that by default Elasticsearch doesn't allow you to deal with pagination for more than 10000 entries. There are two ways to overcome this - either use a Scroll API (refer to the same link on pagination above) or just raise the limit in index settings specifically for this task. The latter is acceptable for this task, but is not the recommended way to do it in production. The query that will help you to set it is below:\n\n```\n~$ curl -XPUT -H \"Content-Type: application/json\" \"http://localhost:9200/places/_settings\" -d '\n{\n  \"index\" : {\n    \"max_result_window\" : 20000\n  }\n}'\n```\n\nAlso, in case 'page' param is specified with a wrong value (outside [0..last_page] or not numeric) your page should return HTTP 400 error and plain text with an error description:\n\n```\nInvalid 'page' value: 'foo'\n```\n\n\u003ch2 id=\"chapter-vi\" \u003eChapter VI\u003c/h2\u003e\n\u003ch3 id=\"ex02\"\u003eExercise 02: Proper API\u003c/h3\u003e\n\nIn modern world most applications prefer APIs over just plain HTML. So, in this exercise all you have to do is implement another handler which responds with `Content-Type: application/json` and JSON version of the same thing as in Ex01 (example for http://127.0.0.1:8888/api/places?page=3):\n\n```\n{\n  \"name\": \"Places\",\n  \"total\": 13649,\n  \"places\": [\n    {\n      \"id\": 65,\n      \"name\": \"AZERBAJDZhAN\",\n      \"address\": \"gorod Moskva, ulitsa Dem'jana Bednogo, dom 4\",\n      \"phone\": \"(495) 946-34-30\",\n      \"location\": {\n        \"lat\": 55.769830485601204,\n        \"lon\": 37.486914061171504\n      }\n    },\n    {\n      \"id\": 69,\n      \"name\": \"Vojazh\",\n      \"address\": \"gorod Moskva, Beskudnikovskij bul'var, dom 57, korpus 1\",\n      \"phone\": \"(499) 485-20-00\",\n      \"location\": {\n        \"lat\": 55.872553383512496,\n        \"lon\": 37.538326789741\n      }\n    },\n    {\n      \"id\": 70,\n      \"name\": \"GBOU Shkola № 1411 (267)\",\n      \"address\": \"gorod Moskva, ulitsa Bestuzhevyh, dom 23\",\n      \"phone\": \"(499) 404-15-09\",\n      \"location\": {\n        \"lat\": 55.87213179130298,\n        \"lon\": 37.609625999999984\n      }\n    },\n    {\n      \"id\": 71,\n      \"name\": \"Zhigulevskoe\",\n      \"address\": \"gorod Moskva, Bibirevskaja ulitsa, dom 7, korpus 1\",\n      \"phone\": \"(964) 565-61-28\",\n      \"location\": {\n        \"lat\": 55.88024342230735,\n        \"lon\": 37.59308635976602\n      }\n    },\n    {\n      \"id\": 75,\n      \"name\": \"Hinkal'naja\",\n      \"address\": \"gorod Moskva, ulitsa Marshala Birjuzova, dom 16\",\n      \"phone\": \"(499) 728-47-01\",\n      \"location\": {\n        \"lat\": 55.79476126986192,\n        \"lon\": 37.491709793339744\n      }\n    },\n    {\n      \"id\": 76,\n      \"name\": \"ShAURMA ZhI\",\n      \"address\": \"gorod Moskva, ulitsa Marshala Birjuzova, dom 19\",\n      \"phone\": \"(903) 018-74-64\",\n      \"location\": {\n        \"lat\": 55.794378830665885,\n        \"lon\": 37.49112002224252\n      }\n    },\n    {\n      \"id\": 80,\n      \"name\": \"Bufet Shkola № 554\",\n      \"address\": \"gorod Moskva, Bolotnikovskaja ulitsa, dom 47, korpus 1\",\n      \"phone\": \"(929) 623-03-21\",\n      \"location\": {\n        \"lat\": 55.66186417434049,\n        \"lon\": 37.58323602169326\n      }\n    },\n    {\n      \"id\": 83,\n      \"name\": \"Kafe\",\n      \"address\": \"gorod Moskva, 1-j Botkinskij proezd, dom 2/6\",\n      \"phone\": \"(495) 945-22-34\",\n      \"location\": {\n        \"lat\": 55.781141341601696,\n        \"lon\": 37.55643137063551\n      }\n    },\n    {\n      \"id\": 84,\n      \"name\": \"STARYJ BATUM'\",\n      \"address\": \"gorod Moskva, ulitsa Akademika Bochvara, dom 7, korpus 1\",\n      \"phone\": \"(495) 942-44-85\",\n      \"location\": {\n        \"lat\": 55.8060307318284,\n        \"lon\": 37.461669109923506\n      }\n    },\n    {\n      \"id\": 89,\n      \"name\": \"Cheburechnaja SSSR\",\n      \"address\": \"gorod Moskva, Bol'shaja Bronnaja ulitsa, dom 27/4\",\n      \"phone\": \"(495) 694-54-76\",\n      \"location\": {\n        \"lat\": 55.764134959774346,\n        \"lon\": 37.60256453956346\n      }\n    }\n  ],\n  \"prev_page\": 2,\n  \"next_page\": 4,\n  \"last_page\": 1364\n}\n```\n\nAlso, in case 'page' param is specified with a wrong value (outside [0..last_page] or not numeric) your API should respond with a corresponding HTTP 400 error and similar JSON:\n\n```\n{\n    \"error\": \"Invalid 'page' value: 'foo'\"\n}\n```\n\n\u003ch2 id=\"chapter-vii\" \u003eChapter VII\u003c/h2\u003e\n\u003ch3 id=\"ex03\"\u003eExercise 03: Closest Restaurants\u003c/h3\u003e\n\nNow let's implement our main piece of functionality - searching for *three* closest restaurants! In order to do that, you'll have to configure sorting for your query:\n\n```\n\"sort\": [\n    {\n      \"_geo_distance\": {\n        \"location\": {\n          \"lat\": 55.674,\n          \"lon\": 37.666\n        },\n        \"order\": \"asc\",\n        \"unit\": \"km\",\n        \"mode\": \"min\",\n        \"distance_type\": \"arc\",\n        \"ignore_unmapped\": true\n      }\n    }\n]\n```\n\nwhere \"lat\" and \"lon\" are your current coordinates. So, for an URL http://127.0.0.1:8888/api/recommend?lat=55.674\u0026lon=37.666 your application should return JSON like this:\n\n```\n{\n  \"name\": \"Recommendation\",\n  \"places\": [\n    {\n      \"id\": 30,\n      \"name\": \"Ryba i mjaso na ugljah\",\n      \"address\": \"gorod Moskva, prospekt Andropova, dom 35A\",\n      \"phone\": \"(499) 612-82-69\",\n      \"location\": {\n        \"lat\": 55.67396575768212,\n        \"lon\": 37.66626689310591\n      }\n    },\n    {\n      \"id\": 3348,\n      \"name\": \"Pizzamento\",\n      \"address\": \"gorod Moskva, prospekt Andropova, dom 37\",\n      \"phone\": \"(499) 612-33-88\",\n      \"location\": {\n        \"lat\": 55.673075576456,\n        \"lon\": 37.664533747576\n      }\n    },\n    {\n      \"id\": 3347,\n      \"name\": \"KOFEJNJa «KAPUChINOFF»\",\n      \"address\": \"gorod Moskva, prospekt Andropova, dom 37\",\n      \"phone\": \"(499) 612-33-88\",\n      \"location\": {\n        \"lat\": 55.672865251005106,\n        \"lon\": 37.6645689561318\n      }\n    }\n  ]\n}\n```\n\n\u003ch2 id=\"chapter-viii\" \u003eChapter VIII\u003c/h2\u003e\n\u003ch3 id=\"ex04\"\u003eExercise 04: JWT\u003c/h3\u003e\n\nSo, the last (but not least) thing that we have to do is to provide some simple form of authentication. Currently the one of the most popular ways of implementing that for an API is by using [JWT](https://jwt.io/introduction/). Luckily, Go has a pretty good set of tooling to deal with it.\n\nFirst, you have to implement an API endpoint http://127.0.0.1:8888/api/get_token which sole purpose will be to generate a token and return it, like this (this is an example, your token will likely be different):\n\n```\n{\n  \"token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZSwiZXhwIjoxNjAxOTc1ODI5LCJuYW1lIjoiTmlrb2xheSJ9.FqsRe0t9YhvEC3hK1pCWumGvrJgz9k9WvhJgO8HsIa8\"\n}\n```\n\nDon't forget to set header 'Content-Type: application/json'.\n\nSecond, you have to protect your `/api/recommend` endpoint with a JWT middleware, that will check the validity of this token.\n\nSo by default when querying this API from the browser it should now fail with an HTTP 401 error, but work when `Authorization: Bearer \u003ctoken\u003e` header is specified by the client (you may check this using cURL or Postman).\n\nThis is a simplest way to provide authentication, no need to go deeper in details for now.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faventhis%2Fgo-bootcamp-elasticsearch-recommender","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faventhis%2Fgo-bootcamp-elasticsearch-recommender","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faventhis%2Fgo-bootcamp-elasticsearch-recommender/lists"}