Squashed commit of the following:
commitremotes/origin/HEADaa1027292eAuthor: Adam Veldhousen <adam.veldhousen@liveauctioneers.com> Date: Tue Nov 19 09:53:21 2019 -0600 tweak into text commit8b1945df7eAuthor: Adam Veldhousen <adam.veldhousen@liveauctioneers.com> Date: Tue Nov 19 09:31:35 2019 -0600 make -accessLog flag actually work commit685a2896abAuthor: Adam Veldhousen <adam.veldhousen@liveauctioneers.com> Date: Tue Nov 19 09:31:07 2019 -0600 update formatting with gofmt, and fix lint errors commit6a74c5f9f0Author: Adam Veldhousen <adam.veldhousen@liveauctioneers.com> Date: Tue Nov 19 08:24:58 2019 -0600 add data generation, version tagging vars to build process commit5822152537Author: Adam Veldhousen <adam.veldhousen@liveauctioneers.com> Date: Tue Nov 19 07:49:22 2019 -0600 add saving shortcuts to local db file commitcb21c3a498Author: Adam Veldhousen <adam.veldhousen@liveauctioneers.com> Date: Mon Nov 18 23:51:21 2019 -0600 added bind address flag commit547bec5eb7Author: Adam Veldhousen <adam.veldhousen@liveauctioneers.com> Date: Mon Nov 18 22:37:06 2019 -0600 refactor shortcut code to be a little nicer commit15667ca704Author: Adam Veldhousen <adam.veldhousen@liveauctioneers.com> Date: Mon Nov 18 00:48:06 2019 -0600 add some stubs for saving user updates to a data file commit4e74fd955bAuthor: Adam Veldhousen <adam.veldhousen@liveauctioneers.com> Date: Mon Nov 18 00:30:29 2019 -0600 added config file loading, fixed bug with search shortcuts
parent
a57f276257
commit
7bb5ad1b5c
|
|
@ -1,2 +1,4 @@
|
|||
.bin
|
||||
.vscode
|
||||
.vscode
|
||||
riffraff
|
||||
data.json
|
||||
|
|
@ -10,9 +10,11 @@ RUN make clean build
|
|||
FROM alpine
|
||||
|
||||
ARG VERSION=dev
|
||||
ARG COMMIT=00000000000000000000
|
||||
|
||||
LABEL maintainer="Adam Veldhousen <adam@vdhsn.com>"
|
||||
LABEL version=${VERSION}
|
||||
LABEL commit=${COMMIT}
|
||||
|
||||
WORKDIR /usr/local/bin
|
||||
COPY --from=build /go/src/riffraff/.bin/riffraff /usr/local/bin/
|
||||
|
|
@ -25,4 +27,4 @@ USER riffraff
|
|||
|
||||
EXPOSE 8080
|
||||
|
||||
ENTRYPOINT [ "/usr/local/bin/riffraff", "-port", "8080", "-accesslog"]
|
||||
ENTRYPOINT [ "/usr/local/bin/riffraff", "-bind", "127.0.0.1", "-port", "8080", "-accesslog"]
|
||||
|
|
@ -8,15 +8,15 @@ import (
|
|||
"sync"
|
||||
)
|
||||
|
||||
func NewServer(tp TemplateRenderer, accessLogging bool) http.Handler {
|
||||
type Shortcuts map[string]string
|
||||
|
||||
func NewServer(tp TemplateRenderer, shortcutStore *ShortcutStore, accessLogging bool) http.Handler {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
shorts, _ := shortcutStore.LoadShortcuts(nil)
|
||||
ss := &CommandHandler{
|
||||
Mutex: &sync.Mutex{},
|
||||
Shortcuts: map[string]string{
|
||||
"*": DefaultSearchProvider,
|
||||
"help": "/",
|
||||
},
|
||||
Mutex: &sync.Mutex{},
|
||||
Shortcuts: shorts,
|
||||
}
|
||||
|
||||
mux.HandleFunc("/", tp.RenderHandler("index.html", ss, nil))
|
||||
|
|
@ -25,27 +25,43 @@ func NewServer(tp TemplateRenderer, accessLogging bool) http.Handler {
|
|||
"Content-Type": []string{"application/opensearchdescription+xml"},
|
||||
}))
|
||||
|
||||
handlerFunc := searchHandler(ss, accessLogging)
|
||||
handlerFunc := searchHandler(ss, shortcutStore, accessLogging)
|
||||
mux.HandleFunc("/search", handlerFunc)
|
||||
mux.HandleFunc("/search_to_home", handlerFunc)
|
||||
|
||||
return mux
|
||||
}
|
||||
|
||||
func searchHandler(scs *CommandHandler, logAccess bool) http.HandlerFunc {
|
||||
func searchHandler(scs *CommandHandler, shortcutStore *ShortcutStore, logAccess bool) http.HandlerFunc {
|
||||
accessLogger := log.New(os.Stdout, "[access] ", log.Ldate|log.Lmicroseconds|log.Lshortfile)
|
||||
|
||||
return func(res http.ResponseWriter, req *http.Request) {
|
||||
v := req.URL.Query()
|
||||
commandString := v.Get("q")
|
||||
|
||||
action, err := scs.Handle(commandString)
|
||||
if err != nil {
|
||||
accessLogger.Printf("'%s' -> got error: '%s'", commandString, err.Error())
|
||||
if logAccess {
|
||||
accessLogger.Printf("'%s' -> got error: '%s'", commandString, err.Error())
|
||||
}
|
||||
http.Redirect(res, req, fmt.Sprintf("https://duckduckgo.com?q=%s", commandString), http.StatusFound)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
accessLogger.Printf("'%s' %s -> 302 %s", commandString, action.Action, action.Location)
|
||||
|
||||
if action.Action != "lookup" {
|
||||
if err := shortcutStore.SaveShortcuts(scs.Shortcuts, nil); err != nil {
|
||||
if logAccess {
|
||||
accessLogger.Printf("'%s' %s -> could not save shortcuts database file: %v", commandString, action.Action, err)
|
||||
}
|
||||
http.Error(res, err.Error(), http.StatusInternalServerError)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
http.Redirect(res, req, action.Location, http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ func (c *CommandHandler) Handle(input string) (Command, error) {
|
|||
rawArgs := strings.Fields(input)
|
||||
fragmentCount := len(rawArgs)
|
||||
farg := "*"
|
||||
parameters := input
|
||||
|
||||
var shortcut string
|
||||
|
||||
|
|
@ -46,7 +45,7 @@ func (c *CommandHandler) Handle(input string) (Command, error) {
|
|||
}
|
||||
}
|
||||
|
||||
return c.getShortcut(farg, parameters), nil
|
||||
return c.getShortcut(farg, rawArgs...), nil
|
||||
}
|
||||
|
||||
func (c *CommandHandler) updateShortcut(action, shortcut, location string) (Command, error) {
|
||||
|
|
@ -84,11 +83,16 @@ func (c *CommandHandler) updateShortcut(action, shortcut, location string) (Comm
|
|||
return command, nil
|
||||
}
|
||||
|
||||
func (c *CommandHandler) getShortcut(key string, parameter string) Command {
|
||||
func (c *CommandHandler) getShortcut(key string, input ...string) Command {
|
||||
var parameter string
|
||||
|
||||
location, ok := c.Shortcuts[key]
|
||||
if !ok {
|
||||
location = DefaultSearchProvider
|
||||
key = "*"
|
||||
parameter = strings.Join(input, " ")
|
||||
} else {
|
||||
parameter = strings.Join(input[1:], " ")
|
||||
}
|
||||
|
||||
if strings.Contains(location, "%s") {
|
||||
|
|
@ -102,6 +106,13 @@ func (c *CommandHandler) getShortcut(key string, parameter string) Command {
|
|||
}
|
||||
}
|
||||
|
||||
func NewDefaultShortcuts() Shortcuts {
|
||||
return Shortcuts{
|
||||
"*": DefaultSearchProvider,
|
||||
"help": "/",
|
||||
}
|
||||
}
|
||||
|
||||
type Command struct {
|
||||
Action string
|
||||
Name string
|
||||
|
|
|
|||
|
|
@ -3,11 +3,12 @@ package internal
|
|||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Handle(t *testing.T) {
|
||||
tests := []struct {
|
||||
testHandleCases := []struct {
|
||||
name string
|
||||
input string
|
||||
want Command
|
||||
|
|
@ -36,6 +37,15 @@ func Test_Handle(t *testing.T) {
|
|||
input: "add gh",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "add search shortcut: 'add so https://stackvoerflow.com?q=%s'",
|
||||
input: "add so https://stackoverflow.com?q=%s",
|
||||
want: Command{
|
||||
Action: "add",
|
||||
Name: "so",
|
||||
Location: "https://stackoverflow.com?q=%s",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove shortcut: 'remove gh'",
|
||||
input: "remove gh",
|
||||
|
|
@ -67,13 +77,25 @@ func Test_Handle(t *testing.T) {
|
|||
Location: "https://facebook.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "visit a search shortcut: 'go net/http'",
|
||||
input: "go net/http",
|
||||
want: Command{
|
||||
Action: "lookup",
|
||||
Name: "go",
|
||||
Location: "https://godoc.org/net/http",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
for _, testCase := range testHandleCases {
|
||||
tt := testCase
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cm := &CommandHandler{
|
||||
Mutex: &sync.Mutex{},
|
||||
Shortcuts: map[string]string{
|
||||
"fb": "https://facebook.com",
|
||||
"go": "https://godoc.org/%s",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,120 @@
|
|||
package internal
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type ShortcutStore struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
type ShortcutData struct {
|
||||
Shortcuts Shortcuts `json:"shortcuts"`
|
||||
}
|
||||
|
||||
func (ss *ShortcutStore) Init() error {
|
||||
defaultSS := NewDefaultShortcuts()
|
||||
|
||||
if _, err := os.Stat(ss.Path); os.IsNotExist(err) {
|
||||
log.Printf("file doesn't exist %s: %v", ss.Path, err)
|
||||
|
||||
if err := ss.SaveShortcuts(defaultSS, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := ss.LoadShortcuts(nil); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *ShortcutStore) SaveShortcuts(shorts Shortcuts, copyTo io.Writer) error {
|
||||
file, err := os.Create(ss.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
temp := Shortcuts{}
|
||||
|
||||
for k, v := range shorts {
|
||||
if k != "help" {
|
||||
temp[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
sc := ShortcutData{Shortcuts: temp}
|
||||
|
||||
var target io.Writer
|
||||
if copyTo != nil {
|
||||
target = io.MultiWriter(file, copyTo)
|
||||
} else {
|
||||
target = file
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(target)
|
||||
encoder.SetIndent("", "\t")
|
||||
|
||||
if err := encoder.Encode(&sc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *ShortcutStore) LoadShortcuts(copyTo io.Writer) (Shortcuts, error) {
|
||||
var sc ShortcutData
|
||||
|
||||
file, err := os.Open(ss.Path)
|
||||
if err != nil {
|
||||
if !os.IsExist(err) {
|
||||
return NewDefaultShortcuts(), nil
|
||||
}
|
||||
|
||||
return sc.Shortcuts, err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
finfo, err := file.Stat()
|
||||
if err != nil {
|
||||
return sc.Shortcuts, err
|
||||
}
|
||||
|
||||
if finfo.Size() == 0 {
|
||||
return NewDefaultShortcuts(), nil
|
||||
}
|
||||
|
||||
var target io.Reader
|
||||
|
||||
if copyTo != nil {
|
||||
target = io.TeeReader(file, copyTo)
|
||||
} else {
|
||||
target = file
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(target)
|
||||
if err := decoder.Decode(&sc); err != nil {
|
||||
return sc.Shortcuts, err
|
||||
}
|
||||
|
||||
if sc.Shortcuts == nil {
|
||||
return NewDefaultShortcuts(), nil
|
||||
}
|
||||
|
||||
sc.Shortcuts["help"] = "/"
|
||||
|
||||
if v, ok := sc.Shortcuts["*"]; !ok || v == "" {
|
||||
sc.Shortcuts["*"] = DefaultSearchProvider
|
||||
}
|
||||
|
||||
return sc.Shortcuts, nil
|
||||
}
|
||||
|
|
@ -72,9 +72,11 @@ func (tr TemplateRenderer) RenderHandler(filename string, ss *CommandHandler, he
|
|||
return func(res http.ResponseWriter, req *http.Request) {
|
||||
templVar := ToTemplateVars(req, ss.Shortcuts)
|
||||
h := res.Header()
|
||||
|
||||
for k, v := range headers {
|
||||
h[k] = v
|
||||
}
|
||||
|
||||
h.Set("Cache-Control", "max-age 0; no-cache; private")
|
||||
|
||||
if err := tr.Render(filename, templVar, res); err != nil {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
</head>
|
||||
<body style="display: flex; width: 100%; align-items: center; flex-direction: column;">
|
||||
<div style="display: flex; align-content: center; flex-direction: column; min-width: 40%;">
|
||||
<h1>Hello internet - {{ .Host }} greets you.</h1>
|
||||
<h1>I was only away for a minute... master.</h1>
|
||||
<!-- Add search box form here for testing/adding shortcuts -->
|
||||
<form action="/search" method="GET" style="display:flex; margin-bottom: 10px; width: 100%;">
|
||||
<input type="text" name="q" value="" style="flex-grow: 1;"/>
|
||||
|
|
|
|||
12
main.go
12
main.go
|
|
@ -12,17 +12,25 @@ import (
|
|||
|
||||
func main() {
|
||||
port := flag.Int("port", 80, "port to listen on")
|
||||
bindAddr := flag.String("bind", "0.0.0.0", "interface to bind to")
|
||||
dbPath := flag.String("data", "./data.json", "path to save shortcut database")
|
||||
enableAccessLogging := flag.Bool("accesslog", true, "Enable access logging")
|
||||
flag.Parse()
|
||||
|
||||
box := packr.NewBox("./internal/templates")
|
||||
tp := internal.TemplateRenderer{FS: box}
|
||||
|
||||
server := internal.NewServer(tp, *enableAccessLogging)
|
||||
ss := &internal.ShortcutStore{Path: *dbPath}
|
||||
|
||||
if err := ss.Init(); err != nil {
|
||||
log.Fatalf("could not access database file: %v", err)
|
||||
}
|
||||
|
||||
server := internal.NewServer(tp, ss, *enableAccessLogging)
|
||||
|
||||
log.SetPrefix("[INFO] ")
|
||||
|
||||
addr := fmt.Sprintf("0.0.0.0:%d", *port)
|
||||
addr := fmt.Sprintf("%s:%d", *bindAddr, *port)
|
||||
log.Printf("Listening @ %s", addr)
|
||||
log.Fatal(http.ListenAndServe(addr, server))
|
||||
}
|
||||
|
|
|
|||
68
makefile
68
makefile
|
|
@ -1,32 +1,74 @@
|
|||
APP := riffraff
|
||||
PKGS := $(shell go list ./... | grep -v vendor)
|
||||
GOBIN := $(GOPATH)/bin
|
||||
LINTBIN := $(GOBIN)/golangci-lint
|
||||
OUTDIR := .bin
|
||||
BINARY := $(OUTDIR)/$(app)
|
||||
|
||||
dev: clean .bin/$(APP)-dev
|
||||
./.bin/$(APP)-dev -port 8080 -accesslog=true
|
||||
GIT_SHA := $$(git rev-parse HEAD)
|
||||
GIT_BRANCH := $$(git rev-parse --abbrev-ref HEAD)
|
||||
VERSION := $(git desribe)
|
||||
|
||||
|
||||
define SHORTCUT_DATA
|
||||
{
|
||||
"shortcuts": {
|
||||
"*": "https://duckduckgo.com/%s",
|
||||
"fb": "https://facebook.com",
|
||||
"gh": "https://github.com",
|
||||
"gitemoji": "https://www.webfx.com/tools/emoji-cheat-sheet/"
|
||||
}
|
||||
}
|
||||
endef
|
||||
export SHORTCUT_DATA
|
||||
|
||||
dev: clean $(BINARY)dev
|
||||
./$(BINARY)-dev -port 8080 -accesslog=true -data=data.json
|
||||
|
||||
test: lint
|
||||
go test -v -cover ./...
|
||||
|
||||
lint: $(LINTBIN)
|
||||
$(LINTBIN) run -p format -p unused -p bugs -p performance
|
||||
|
||||
$(LINTBIN):
|
||||
@GO111MODULE=off go get github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
|
||||
package:
|
||||
docker build --build-arg VERSION=$${VERSION:-dev} -t vdhsn/$(APP):$${VERSION:-dev} .
|
||||
docker build --build-arg VERSION=$(VERSION) \
|
||||
--build-arg COMMIT=$(GIT_SHA) \
|
||||
-t vdhsn/$(APP):$(VERSION) .
|
||||
docker tag vdhsn/$(APP):$(VERSION) vdhsn/$(APP):$(GIT_SHA)
|
||||
docker tag vdhsn/$(APP):$(VERSION) vdhsn/$(APP):$(GIT_BRANCH)
|
||||
|
||||
package-run:
|
||||
docker run -it --rm --name riffraff -u 1000:1000 -p 8080:8080 vdhsn/$(APP):$${VERSION:-dev}
|
||||
docker run -it --rm --name riffraff -u 1000:1000 -p 8080:8080 vdhsn/$(APP):$(GIT_SHA)
|
||||
|
||||
publish: package
|
||||
docker push vdhsn/$(APP):$${VERSION:-dev}
|
||||
docker push vdhsn/$(APP):$(VERSION)
|
||||
docker push vdhsn/$(APP):$(GIT_BRANCH)
|
||||
|
||||
build: .bin/$(APP)
|
||||
build: $(BINARY)
|
||||
|
||||
.bin:
|
||||
mkdir .bin
|
||||
$(OUTDIR):
|
||||
mkdir $@
|
||||
|
||||
.bin/$(APP): packr .bin
|
||||
packr build -o .bin/$(APP) -v .
|
||||
$(BINARY): packr .bin
|
||||
packr build -o $@ -v .
|
||||
|
||||
.bin/$(APP)-dev: .bin
|
||||
go build -o .bin/$(APP)-dev -v .
|
||||
$(BINARY)-dev: .bin
|
||||
go build -o $(BINARY)-dev -v .
|
||||
|
||||
clean:
|
||||
rm -rf .bin
|
||||
|
||||
clobber: clean
|
||||
rm -rf data.json
|
||||
|
||||
data.json:
|
||||
@echo "$${SHORTCUT_DATA}" > data.json
|
||||
|
||||
packr:
|
||||
go get -u github.com/gobuffalo/packr/packr
|
||||
|
||||
.PHONY: build clean dev package package-run publish packr
|
||||
.PHONY: build clean clobber dev lint package package-run publish packr test
|
||||
|
|
|
|||
Loading…
Reference in New Issue