-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor and add support for admin management frontend.
- Move Lang{} definitions to `search` package. - Add several CRUD API endpoints (SQL queries, search.* methods) for managing entries and relations. - Add `/api/config`. - Add HTML template handlers for serving `/admin/*` pages. - Refactor `Tokenizer` interface functions. - Remove redundant `types[]` param from `Entry`. - Refactor `Entry.Relations` into a new `Relations` type. - Add `search.TokensToTSVector()` for tokenizer plugins to share.
- Loading branch information
Showing
11 changed files
with
793 additions
and
145 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,232 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"net/http" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/go-chi/chi" | ||
"github.com/knadh/dictmaker/internal/search" | ||
) | ||
|
||
// handleGetConfig returns the language configuration. | ||
func handleGetConfig(w http.ResponseWriter, r *http.Request) { | ||
var ( | ||
app = r.Context().Value("app").(*App) | ||
) | ||
|
||
sendResponse(app.lang, http.StatusOK, w) | ||
out := struct { | ||
RootURL string `json:"root_url"` | ||
Languages search.LangMap `json:"languages"` | ||
Version string `json:"version"` | ||
}{app.constants.RootURL, app.search.Langs, buildString} | ||
|
||
sendResponse(out, http.StatusOK, w) | ||
} | ||
|
||
// handleGetStats returns DB statistics. | ||
func handleGetStats(w http.ResponseWriter, r *http.Request) { | ||
var ( | ||
app = r.Context().Value("app").(*App) | ||
) | ||
|
||
out, err := app.search.GetStats() | ||
if err != nil { | ||
sendErrorResponse(err.Error(), http.StatusInternalServerError, nil, w) | ||
return | ||
} | ||
|
||
sendResponse(out, http.StatusOK, w) | ||
} | ||
|
||
func handleAdminEntryPage(w http.ResponseWriter, r *http.Request) { | ||
var ( | ||
app = r.Context().Value("app").(*App) | ||
) | ||
|
||
sendTpl(http.StatusOK, "entry", app.adminTpl, nil, w) | ||
} | ||
|
||
// handleInsertEntry inserts a new dictionary entry. | ||
func handleInsertEntry(w http.ResponseWriter, r *http.Request) { | ||
var ( | ||
app = r.Context().Value("app").(*App) | ||
) | ||
|
||
var e search.Entry | ||
if err := json.NewDecoder(r.Body).Decode(&e); err != nil { | ||
sendErrorResponse(fmt.Sprintf("error parsing request: %v", err), http.StatusBadRequest, nil, w) | ||
return | ||
} | ||
|
||
if err := validateEntry(e); err != nil { | ||
sendErrorResponse(err.Error(), http.StatusBadRequest, nil, w) | ||
return | ||
} | ||
|
||
guid, err := app.search.InsertEntry(e) | ||
if err != nil { | ||
sendErrorResponse(fmt.Sprintf("error inserting entry: %v", err), http.StatusInternalServerError, nil, w) | ||
return | ||
} | ||
|
||
// Proxy to the get request to respond with the newly inserted entry. | ||
ctx := chi.RouteContext(r.Context()) | ||
ctx.URLParams.Keys = append(ctx.URLParams.Keys, "guid") | ||
ctx.URLParams.Values = append(ctx.URLParams.Values, guid) | ||
|
||
handleGetEntry(w, r) | ||
} | ||
|
||
// handleUpdateEntry updates a dictionary entry. | ||
func handleUpdateEntry(w http.ResponseWriter, r *http.Request) { | ||
var ( | ||
app = r.Context().Value("app").(*App) | ||
guid = chi.URLParam(r, "guid") | ||
) | ||
|
||
var e search.Entry | ||
if err := json.NewDecoder(r.Body).Decode(&e); err != nil { | ||
sendErrorResponse(fmt.Sprintf("error parsing request: %v", err), http.StatusBadRequest, nil, w) | ||
return | ||
} | ||
|
||
if err := app.search.UpdateEntry(guid, e); err != nil { | ||
sendErrorResponse(fmt.Sprintf("error updating entry: %v", err), http.StatusInternalServerError, nil, w) | ||
return | ||
} | ||
|
||
sendResponse(app.search.Langs, http.StatusOK, w) | ||
} | ||
|
||
// handleDeleteEntry deletes a dictionary entry. | ||
func handleDeleteEntry(w http.ResponseWriter, r *http.Request) { | ||
var ( | ||
app = r.Context().Value("app").(*App) | ||
guid = chi.URLParam(r, "guid") | ||
) | ||
|
||
if err := app.search.DeleteEntry(guid); err != nil { | ||
sendErrorResponse(fmt.Sprintf("error deleting entry: %v", err), http.StatusInternalServerError, nil, w) | ||
return | ||
} | ||
|
||
sendResponse(true, http.StatusOK, w) | ||
} | ||
|
||
// handleAddRelation updates a relation's properties. | ||
func handleAddRelation(w http.ResponseWriter, r *http.Request) { | ||
var ( | ||
app = r.Context().Value("app").(*App) | ||
fromGuid = chi.URLParam(r, "fromGuid") | ||
toGuid = chi.URLParam(r, "toGuid") | ||
) | ||
|
||
var rel search.Relation | ||
if err := json.NewDecoder(r.Body).Decode(&rel); err != nil { | ||
sendErrorResponse(fmt.Sprintf("error parsing request: %v", err), http.StatusBadRequest, nil, w) | ||
return | ||
} | ||
|
||
if err := app.search.InsertRelation(fromGuid, toGuid, rel); err != nil { | ||
sendErrorResponse(fmt.Sprintf("error updating relation: %v", err), http.StatusInternalServerError, nil, w) | ||
return | ||
} | ||
|
||
sendResponse(app.search.Langs, http.StatusOK, w) | ||
} | ||
|
||
// handleUpdateRelation updates a relation's properties. | ||
func handleUpdateRelation(w http.ResponseWriter, r *http.Request) { | ||
var ( | ||
app = r.Context().Value("app").(*App) | ||
relID, _ = strconv.Atoi(chi.URLParam(r, "relID")) | ||
) | ||
|
||
var rel search.Relation | ||
if err := json.NewDecoder(r.Body).Decode(&rel); err != nil { | ||
sendErrorResponse(fmt.Sprintf("error parsing request: %v", err), http.StatusBadRequest, nil, w) | ||
return | ||
} | ||
|
||
if err := app.search.UpdateRelation(relID, rel); err != nil { | ||
sendErrorResponse(fmt.Sprintf("error updating relation: %v", err), http.StatusInternalServerError, nil, w) | ||
return | ||
} | ||
|
||
sendResponse(app.search.Langs, http.StatusOK, w) | ||
} | ||
|
||
// handleReorderRelations reorders the weights of the relation IDs in the given order. | ||
func handleReorderRelations(w http.ResponseWriter, r *http.Request) { | ||
var ( | ||
app = r.Context().Value("app").(*App) | ||
) | ||
|
||
// ids := struct { | ||
// IDs []int `json:ids` | ||
// }{} | ||
var ids []int | ||
if err := json.NewDecoder(r.Body).Decode(&ids); err != nil { | ||
sendErrorResponse(fmt.Sprintf("error parsing request: %v", err), http.StatusBadRequest, nil, w) | ||
return | ||
} | ||
|
||
if err := app.search.ReorderRelations(ids); err != nil { | ||
sendErrorResponse(fmt.Sprintf("error reordering relations: %v", err), http.StatusInternalServerError, nil, w) | ||
return | ||
} | ||
|
||
sendResponse(true, http.StatusOK, w) | ||
} | ||
|
||
// handleDeleteRelation deletes a relation between two entres. | ||
func handleDeleteRelation(w http.ResponseWriter, r *http.Request) { | ||
var ( | ||
app = r.Context().Value("app").(*App) | ||
fromGuid = chi.URLParam(r, "fromGuid") | ||
toGuid = chi.URLParam(r, "toGuid") | ||
) | ||
|
||
if err := app.search.DeleteRelation(fromGuid, toGuid); err != nil { | ||
sendErrorResponse(fmt.Sprintf("error deleting entry: %v", err), http.StatusInternalServerError, nil, w) | ||
return | ||
} | ||
|
||
sendResponse(true, http.StatusOK, w) | ||
} | ||
|
||
func adminPage(tpl string) http.HandlerFunc { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
var ( | ||
app = r.Context().Value("app").(*App) | ||
) | ||
|
||
title := "" | ||
switch tpl { | ||
case "search": | ||
title = fmt.Sprintf("Search '%s'", r.URL.Query().Get("query")) | ||
} | ||
|
||
sendTpl(http.StatusOK, tpl, app.adminTpl, struct { | ||
Title string | ||
}{title}, w) | ||
}) | ||
} | ||
|
||
func validateEntry(e search.Entry) error { | ||
if strings.TrimSpace(e.Content) == "" { | ||
return errors.New("invalid `content`.") | ||
} | ||
if strings.TrimSpace(e.Initial) == "" { | ||
return errors.New("invalid `initial`.") | ||
} | ||
if strings.TrimSpace(e.Lang) == "" { | ||
return errors.New("invalid `lang`.") | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.