diff options
| -rw-r--r-- | .gitignore | 2 | ||||
| -rw-r--r-- | Makefile | 25 | ||||
| -rw-r--r-- | active_client.go | 2 | ||||
| -rw-r--r-- | api_client.go | 92 | ||||
| -rw-r--r-- | api_client_test.go | 103 | ||||
| -rw-r--r-- | client.go | 42 | ||||
| -rw-r--r-- | cmd/www-kbbi/main.go | 31 | ||||
| -rw-r--r-- | daftar_kata.go | 4 | ||||
| -rw-r--r-- | definisi_kata.go | 4 | ||||
| -rw-r--r-- | definisi_response.go | 10 | ||||
| -rw-r--r-- | direct_client.go | 14 | ||||
| -rw-r--r-- | go.mod | 2 | ||||
| -rw-r--r-- | go.sum | 4 | ||||
| -rw-r--r-- | kamus_cache.go | 165 | ||||
| -rw-r--r-- | kamus_cache_test.go | 36 | ||||
| -rw-r--r-- | kbbi.go | 10 | ||||
| -rw-r--r-- | kbbi_test.go | 59 | ||||
| -rw-r--r-- | server.go | 167 |
18 files changed, 737 insertions, 35 deletions
@@ -1,3 +1,5 @@ /internal/cmd/mergedic/id_ID.dic /internal/cmd/mergedic/id_ID.dic.new /kbbi +/testdata/kamus.gob +/testdata/kamus.gob.new diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..192eed6 --- /dev/null +++ b/Makefile @@ -0,0 +1,25 @@ +## Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +## Use of this source code is governed by a BSD-style +## license that can be found in the LICENSE file. + +.PHONY: all build lint test + +LINT_OPTS = + +all: build lint test + +build: + go build ./... + +lint: + golangci-lint run --enable-all \ + --disable=dupl \ + --disable=funlen \ + --disable=godox \ + --disable=gomnd \ + --disable=wsl \ + --disable=gocognit \ + ./... + +test: + go test ./... diff --git a/active_client.go b/active_client.go index 061bd1d..0aebd0b 100644 --- a/active_client.go +++ b/active_client.go @@ -9,5 +9,5 @@ package kbbi // type activeClient interface { CariDefinisi(words []string) (res DefinisiResponse, err error) - ListKataDasar() (kataDasar daftarKata, err error) + ListKataDasar() (kataDasar DaftarKata, err error) } diff --git a/api_client.go b/api_client.go new file mode 100644 index 0000000..eb1c2f7 --- /dev/null +++ b/api_client.go @@ -0,0 +1,92 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kbbi + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/url" + "strings" +) + +// +// apiClient is client that connect to cached server API. +// +type apiClient struct { + url string + conn *http.Client +} + +// +// newAPIClient create a new client for server API. +// If url is empty it will use default server API. +// +func newAPIClient(url string) (client *apiClient) { + if len(url) == 0 { + url = defServerAPI + } + + client = &apiClient{ + url: url, + conn: &http.Client{ + Timeout: defTimeout, + }, + } + + return client +} + +// +// Lookup the definition of words through server API. +// +func (client *apiClient) CariDefinisi(words []string) ( + res DefinisiResponse, err error, +) { + if len(words) == 0 { + return nil, nil + } + + params := url.Values{} + params.Set(paramNameKata, strings.Join(words, ",")) + + req, err := http.NewRequest(http.MethodGet, client.url+pathAPIDefinisi, nil) + if err != nil { + return nil, fmt.Errorf("CariDefinisi: %w", err) + } + + req.URL.RawQuery = params.Encode() + + httpRes, err := client.conn.Do(req) + if err != nil { + return nil, fmt.Errorf("CariDefinisi: %w", err) + } + + resBody, err := ioutil.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("CariDefinisi: %w", err) + } + + defer httpRes.Body.Close() + + if len(resBody) == 0 { + return res, nil + } + + err = res.unpack(resBody) + if err != nil { + return nil, fmt.Errorf("CariDefinisi: %w", err) + } + + return res, nil +} + +// +// ListKataDasar list all of the root words in dictionary. +// +func (client *apiClient) ListKataDasar() (res DaftarKata, err error) { + //TODO: return cached list. + return res, nil +} diff --git a/api_client_test.go b/api_client_test.go new file mode 100644 index 0000000..23e1e81 --- /dev/null +++ b/api_client_test.go @@ -0,0 +1,103 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kbbi + +import ( + "testing" + + "github.com/shuLhan/share/lib/test" +) + +func TestApiClient_CariDefinisi_offline(t *testing.T) { + testServer.offline = true + + client := newAPIClient(testServerAPI) + + cases := []struct { + desc string + words []string + exp DefinisiResponse + expError string + }{{ + desc: "With empty input", + }, { + desc: "With words not found", + words: []string{"a"}, + }, { + desc: "With valid word in cache", + words: []string{"mengeja"}, + exp: DefinisiResponse{ + "mengeja": testKataMengeja, + }, + }, { + desc: "With duplicate words", + words: []string{"mengeja", "mengeja"}, + exp: DefinisiResponse{ + "mengeja": testKataMengeja, + }, + }} + + for _, c := range cases { + t.Logf(c.desc) + + got, err := client.CariDefinisi(c.words) + if err != nil { + test.Assert(t, "error", c.expError, err.Error(), true) + continue + } + + test.Assert(t, "DefinisiResponse", c.exp, got, true) + } +} + +func TestApiClient_CariDefinisi_online(t *testing.T) { + testServer.offline = false + + client := newAPIClient(testServerAPI) + + cases := []struct { + desc string + words []string + exp DefinisiResponse + expError string + }{{ + desc: "With empty input", + }, { + desc: "With valid word in cache", + words: []string{"mengeja"}, + exp: DefinisiResponse{ + "mengeja": testKataMengeja, + }, + }, { + desc: "With duplicate words", + words: []string{"mengeja", "mengeja"}, + exp: DefinisiResponse{ + "mengeja": testKataMengeja, + }, + }, { + desc: "With one of the word not in cache", + words: []string{"mengeja", "eja"}, + exp: DefinisiResponse{ + "mengeja": testKataMengeja, + "eja": testKataEja, + }, + }} + + for _, c := range cases { + t.Logf(c.desc) + + got, err := client.CariDefinisi(c.words) + if err != nil { + test.Assert(t, "error", c.expError, err.Error(), true) + continue + } + + for k, v := range got { + t.Logf("got: %s = %+v", k, v) + } + + test.Assert(t, "DefinisiResponse", c.exp, got, true) + } +} @@ -4,13 +4,16 @@ package kbbi -import "net/http" +import ( + "net/http" +) // -// Client for dictionary API and official KBBI servers. +// Client for dictionary API and official KBBI server. // type Client struct { active activeClient + api *apiClient direct *directClient } @@ -21,13 +24,17 @@ type Client struct { func NewClient(cookies []*http.Cookie) (cl *Client, err error) { cl = &Client{} + cl.direct, err = newDirectClient(cookies) + if err != nil { + return nil, err + } + + cl.api = newAPIClient("") + if cookies != nil { - cl.direct, err = newDirectClient(cookies) - if err != nil { - return nil, err - } cl.active = cl.direct - return cl, nil + } else { + cl.active = cl.api } return cl, nil @@ -43,29 +50,28 @@ func (cl *Client) CariDefinisi(words []string) ( return cl.active.CariDefinisi(words) } - // TODO: start with api client first ... - - cl.direct, err = newDirectClient(nil) + res, err = cl.api.CariDefinisi(words) if err != nil { - return nil, err + return cl.direct.CariDefinisi(words) } - return cl.direct.CariDefinisi(words) + return res, nil } -func (cl *Client) ListKataDasar() (kataDasar daftarKata, err error) { +// +// ListKataDasar list all of the root words in dictionary. +// +func (cl *Client) ListKataDasar() (res DaftarKata, err error) { if cl.active != nil { return cl.active.ListKataDasar() } - // TODO: start with api client first ... - - cl.direct, err = newDirectClient(nil) + res, err = cl.api.ListKataDasar() if err != nil { - return nil, err + return cl.direct.ListKataDasar() } - return cl.direct.ListKataDasar() + return res, nil } // diff --git a/cmd/www-kbbi/main.go b/cmd/www-kbbi/main.go new file mode 100644 index 0000000..bb4cd86 --- /dev/null +++ b/cmd/www-kbbi/main.go @@ -0,0 +1,31 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "log" + "os" + "os/signal" + + "github.com/shuLhan/kbbi" +) + +func main() { + server, err := kbbi.NewServer() + if err != nil { + log.Fatal(err) + } + + go func() { + err = server.Start() + if err != nil { + log.Println(err) + } + }() + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + <-c +} diff --git a/daftar_kata.go b/daftar_kata.go index ba407f9..1560355 100644 --- a/daftar_kata.go +++ b/daftar_kata.go @@ -4,12 +4,12 @@ package kbbi -type daftarKata map[string]struct{} +type DaftarKata map[string]struct{} // // merge other map into current map. // -func (dk daftarKata) merge(in daftarKata) daftarKata { +func (dk DaftarKata) merge(in DaftarKata) DaftarKata { for k := range in { dk[k] = struct{}{} } diff --git a/definisi_kata.go b/definisi_kata.go index 26b78f7..ac8ba5d 100644 --- a/definisi_kata.go +++ b/definisi_kata.go @@ -17,8 +17,8 @@ import ( // type DefinisiKata struct { Isi string `json:"isi"` - Kelas []string `json:"kelas"` - Contoh []string `json:"contoh"` + Kelas []string `json:"kelas,omitempty"` + Contoh []string `json:"contoh,omitempty"` } func parseDefinisiKata(li *html.Node) (defKata *DefinisiKata) { diff --git a/definisi_response.go b/definisi_response.go index d842310..91808eb 100644 --- a/definisi_response.go +++ b/definisi_response.go @@ -4,8 +4,18 @@ package kbbi +import "encoding/json" + // // DefinisiResponse is a response from "/definisi" API. // Its contains mapping of words and their definitions. // type DefinisiResponse map[string]*Kata + +func (res *DefinisiResponse) pack() ([]byte, error) { + return json.Marshal(res) +} + +func (res *DefinisiResponse) unpack(v []byte) (err error) { + return json.Unmarshal(v, res) +} diff --git a/direct_client.go b/direct_client.go index 3123829..7c944e0 100644 --- a/direct_client.go +++ b/direct_client.go @@ -14,7 +14,6 @@ import ( "net/url" "strconv" "strings" - "time" "github.com/shuLhan/share/lib/debug" "golang.org/x/net/html" @@ -23,7 +22,6 @@ import ( const ( maxPageNumber = 501 - defTimeout = 20 * time.Second ) // @@ -41,7 +39,7 @@ type directClient struct { func newDirectClient(cookies []*http.Cookie) (cl *directClient, err error) { cookieURL, err := url.Parse(baseURL) if err != nil { - return nil, fmt.Errorf("New: %w", err) + return nil, fmt.Errorf("newDirectClient: %w", err) } jarOpt := &cookiejar.Options{ @@ -114,9 +112,9 @@ func (cl *directClient) CariDefinisi(ins []string) ( } // -// ListKataDasar get list of kata dasar +// ListKataDasar list all of the root words in dictionary. // -func (cl *directClient) ListKataDasar() (kataDasar daftarKata, err error) { +func (cl *directClient) ListKataDasar() (kataDasar DaftarKata, err error) { params := url.Values{ paramNameMasukan: []string{paramValueDasar}, paramNameMasukanLengkap: []string{paramValueDasar}, @@ -124,7 +122,7 @@ func (cl *directClient) ListKataDasar() (kataDasar daftarKata, err error) { urlPage := baseURL + "/Cari/Jenis?" - kataDasar = make(daftarKata) + kataDasar = make(DaftarKata) for pageNumber := 1; pageNumber <= maxPageNumber; pageNumber++ { params.Set(paramNamePage, strconv.Itoa(pageNumber)) @@ -224,14 +222,14 @@ func (cl *directClient) setCookies(cookies []*http.Cookie) { } func (cl *directClient) parseHTMLKataDasar(htmlBody []byte) ( - kataDasar daftarKata, err error, + kataDasar DaftarKata, err error, ) { node, err := html.Parse(bytes.NewReader(htmlBody)) if err != nil { return nil, err } - kataDasar = make(daftarKata) + kataDasar = make(DaftarKata) var prev *html.Node @@ -3,7 +3,7 @@ module github.com/shuLhan/kbbi go 1.13 require ( - github.com/shuLhan/share v0.13.1-0.20200328211544-f4912dbb53e0 + github.com/shuLhan/share v0.13.1-0.20200330125604-7ac43c699173 golang.org/x/net v0.0.0-20200320220750-118fecf932d8 ) @@ -1,5 +1,5 @@ -github.com/shuLhan/share v0.13.1-0.20200328211544-f4912dbb53e0 h1:/yJph5LPHE21i+A5B8PBthxbAjRx50V+28af/I64vN4= -github.com/shuLhan/share v0.13.1-0.20200328211544-f4912dbb53e0/go.mod h1:uG1C5VfU81bI4iQ48VbWRm5c7mkvpr4huuUO54PKK1o= +github.com/shuLhan/share v0.13.1-0.20200330125604-7ac43c699173 h1:lhiuIUynM8i0EdntUiy0gnyBcvRmkyrXkdQBPDf0iJw= +github.com/shuLhan/share v0.13.1-0.20200330125604-7ac43c699173/go.mod h1:uG1C5VfU81bI4iQ48VbWRm5c7mkvpr4huuUO54PKK1o= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200320181102-891825fb96df/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= diff --git a/kamus_cache.go b/kamus_cache.go new file mode 100644 index 0000000..ffcf4db --- /dev/null +++ b/kamus_cache.go @@ -0,0 +1,165 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kbbi + +import ( + "bytes" + "encoding/gob" + "errors" + "io/ioutil" + "log" + "os" + "sync" + + libio "github.com/shuLhan/share/lib/io" +) + +const ( + defStorageName = "kamus.gob" +) + +// +// kamusCache contains cache of words and its definitions. +// +type kamusCache struct { + sync.Mutex + cache map[string]*Kata + lastSize int + + storagePath string +} + +// +// newKamusCache create and initialize the cache for dictionary. +// +func newKamusCache(storagePath string) (kamusc *kamusCache, err error) { + if len(storagePath) == 0 { + storagePath = defStorageName + } + + kamusc = &kamusCache{ + cache: make(map[string]*Kata), + storagePath: storagePath, + } + + err = kamusc.load() + if err != nil { + return nil, err + } + + return kamusc, nil +} + +// +// get the definition of word from cache or nil if not exist. +// +func (kamus *kamusCache) get(word string) (kata *Kata) { + kamus.Lock() + kata = kamus.cache[word] + kamus.Unlock() + return kata +} + +// +// isChanging will return true if the last cache size is not equal with +// current size. +// +func (kamus *kamusCache) isChanging() bool { + kamus.Lock() + defer kamus.Unlock() + return kamus.lastSize != len(kamus.cache) +} + +// +// load the cached dictionary from storage. +// +func (kamus *kamusCache) load() (err error) { + kamus.Lock() + defer kamus.Unlock() + + v, err := ioutil.ReadFile(kamus.storagePath) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil + } + return err + } + + r := bytes.NewReader(v) + + dec := gob.NewDecoder(r) + err = dec.Decode(&kamus.cache) + if err != nil { + return err + } + + kamus.lastSize = len(kamus.cache) + + return nil +} + +// +// set save the definition of word into cache. +// +func (kamus *kamusCache) set(word string, kata *Kata) { + if len(word) == 0 || kata == nil { + return + } + + kamus.Lock() + kamus.cache[word] = kata + kamus.Unlock() +} + +// +// store the cache to file only if the storage path is set. +// +func (kamus *kamusCache) store() (err error) { + if len(kamus.storagePath) == 0 { + return nil + } + + kamus.Lock() + defer kamus.Unlock() + + if len(kamus.cache) == 0 { + return nil + } + + newStorage := kamus.storagePath + ".new" + + f, err := os.Create(newStorage) + if err != nil { + errc := f.Close() + if errc != nil { + log.Println("kamusCache: store: ", err) + } + return err + } + + enc := gob.NewEncoder(f) + err = enc.Encode(&kamus.cache) + if err != nil { + errc := f.Close() + if errc != nil { + log.Println("kamusCache: store: ", err) + } + return err + } + + errc := f.Close() + if errc != nil { + log.Println("kamusCache: store: ", err) + } + + err = libio.Copy(kamus.storagePath, newStorage) + if err != nil { + return err + } + + kamus.lastSize = len(kamus.cache) + + return nil +} diff --git a/kamus_cache_test.go b/kamus_cache_test.go new file mode 100644 index 0000000..fbeda02 --- /dev/null +++ b/kamus_cache_test.go @@ -0,0 +1,36 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kbbi + +import ( + "testing" + + "github.com/shuLhan/share/lib/test" +) + +const ( + testStoragePath = "testdata/kamus.gob" +) + +func TestKamusCache_store_load(t *testing.T) { + exp, err := newKamusCache(testStoragePath) + if err != nil { + t.Fatal(err) + } + + exp.set("mengeja", testKataMengeja) + + err = exp.store() + if err != nil { + t.Fatal(err) + } + + got, err := newKamusCache(testStoragePath) + if err != nil { + t.Fatal(err) + } + + test.Assert(t, "store and load", exp, got, true) +} @@ -8,12 +8,17 @@ // package kbbi +import "time" + const ( hostname = "kbbi.kemdikbud.go.id" baseURL = "https://" + hostname loginURL = baseURL + "/Account/Login" entriPath = "/entri/" + defServerAPI = "https://kilabit.info/project/kbbi" + pathAPIDefinisi = "/api/definisi" + attrNameClass = "class" attrNameHref = "href" attrNameName = "name" @@ -35,13 +40,16 @@ const ( tagNameUnorderedList = "ul" paramNameIngatSaya = "IngatSaya" + paramNameKata = "kata" paramNameKataSandi = "KataSandi" paramNameMasukan = "masukan" paramNameMasukanLengkap = "masukanLengkap" paramNamePage = "page" paramNamePosel = "Posel" - paramNameRequestVerificationToken = "__RequestVerificationToken" + paramNameRequestVerificationToken = "__RequestVerificationToken" //nolint: gosec paramValueDasar = "dasar" paramValueFalse = "false" + + defTimeout = 20 * time.Second ) diff --git a/kbbi_test.go b/kbbi_test.go new file mode 100644 index 0000000..ddd4efe --- /dev/null +++ b/kbbi_test.go @@ -0,0 +1,59 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kbbi + +import ( + "log" + "os" + "testing" +) + +const ( + testServerAPI = "http://127.0.0.1" + defListen +) + +//nolint: gochecknoglobals +var ( + testServer *Server + + testKataMengeja = &Kata{ + Dasar: "eja", + Definisi: []*DefinisiKata{{ + Isi: "melafalkan (menyebutkan) huruf-huruf satu demi satu", + Kelas: []string{"Verba: kata kerja"}, + Contoh: []string{ + `kita ~ kata “dapat” dengan “d-a-p-a-t”`, + }, + }}, + } + + testKataEja = &Kata{ + Definisi: []*DefinisiKata{{ + Isi: "lafal huruf satu demi satu", + Kelas: []string{"Verba: kata kerja"}, + }}, + } +) + +func TestMain(m *testing.M) { + var err error + + // Run the local server to test the apiClient. + testServer, err = NewServer() + if err != nil { + log.Fatal(err) + } + + testServer.kamus.set("mengeja", testKataMengeja) + + go func() { + err := testServer.Start() + if err != nil { + log.Fatal(err) + } + }() + + os.Exit(m.Run()) +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..a241f05 --- /dev/null +++ b/server.go @@ -0,0 +1,167 @@ +// Copyright 2020, Shulhan <m.shulhan@gmail.com>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package kbbi + +import ( + "fmt" + "log" + stdhttp "net/http" + "strings" + "time" + + "github.com/shuLhan/share/lib/http" +) + +const ( + defListen = ":3394" + emptyResponse = "{}" +) + +// +// Server for KBBI with caching and spell checking functionalities. +// +type Server struct { + http *http.Server + kamus *kamusCache + + // The client that forward request to official KBBI server. + forwardc *directClient + + // If offline is true and the word definition is not found on cache, + // the server will not forward request to official KBBI server. + offline bool +} + +// +// NewServer create and initialize the server. +// +func NewServer() (server *Server, err error) { + opts := &http.ServerOptions{ + Address: defListen, + } + + server = &Server{} + + server.kamus, err = newKamusCache("") + if err != nil { + return nil, fmt.Errorf("http.NewServer: %w", err) + } + + server.http, err = http.NewServer(opts) + if err != nil { + return nil, fmt.Errorf("http.NewServer: %w", err) + } + + server.forwardc, err = newDirectClient(nil) + if err != nil { + return nil, fmt.Errorf("http.NewServer: %w", err) + } + + err = server.registerEndpoints() + if err != nil { + return nil, fmt.Errorf("http.NewServer: %w", err) + } + + return server, nil +} + +// +// Start the HTTP server. +// +func (server *Server) Start() (err error) { + go server.dumpCache() + + return server.http.Start() +} + +// +// dumpCache periodically dump the cache to file to be loaded later. +// +func (server *Server) dumpCache() { + ticker := time.NewTicker(1 * time.Hour) + + for range ticker.C { + if !server.kamus.isChanging() { + continue + } + err := server.kamus.store() + if err != nil { + log.Println("server.dumpCache: ", err) + } + } +} + +func (server *Server) handleDefinisi( + httpRes stdhttp.ResponseWriter, + httpReq *stdhttp.Request, + reqBody []byte, +) (resBody []byte, err error) { + paramKata := httpReq.Form.Get(paramNameKata) + if len(paramKata) == 0 { + return []byte(emptyResponse), nil + } + + inputs := strings.Split(paramKata, ",") + res := make(DefinisiResponse, len(inputs)) + + for _, in := range inputs { + in = strings.TrimSpace(in) + if len(in) == 0 { + continue + } + + kata := server.kamus.get(in) + if kata != nil { + res[in] = kata + continue + } + + if server.offline { + continue + } + + // The word does not exist in cache, retrieve it from official + // website. + fwRes, err := server.forwardc.CariDefinisi([]string{in}) + if err != nil { + kata.err = err + continue + } + + for k, v := range fwRes { + if k == in { + res[in] = v + server.kamus.set(k, v) + delete(fwRes, k) + break + } + } + } + + if len(res) == 0 { + return []byte(emptyResponse), nil + } + + resBody, err = res.pack() + if err != nil { + return nil, err + } + + return resBody, nil +} + +// +// registerEndpoints register the API endpoints. +// +func (server *Server) registerEndpoints() (err error) { + epDefinisi := &http.Endpoint{ + Method: http.RequestMethodGet, + Path: pathAPIDefinisi, + RequestType: http.RequestTypeQuery, + ResponseType: http.ResponseTypeJSON, + Call: server.handleDefinisi, + } + return server.http.RegisterEndpoint(epDefinisi) +} |
