diff --git a/README.md b/README.md index fa39e75..09dc7a6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # burgoking +⚠️ As of September 2022, Burger King migrated their survey system to a third party company, resulting to changes that require an almost complete rebuild of this project. Unfortunately, I cannot find the time nor the will to do it. So, I replaced the generation algorithm with something local and static. In almost all cases, this generator will work (I never encountered a BK employee that checked the numbers). ⚠️ + 🍔 **Burger King - Free Burger Code Generator** 🍔 Generate a Burger King's promotion code to get a free burger using Golang. @@ -74,9 +76,9 @@ Usage of code: The [api](https://github.com/Scotow/burgoking/blob/master/cmd/api) command starts a simple web server that returns a new promotion code to each request. -##### Web friendly server +##### (Old) Web friendly server -The [web](https://github.com/Scotow/burgoking/blob/master/cmd/web) command is a ready-for-demo binary that serves a simple, yet beautiful UI generating promotion codes using a pool. +The [web](https://github.com/Scotow/burgoking/blob/master/cmd/web_original) command is a ready-for-demo binary that serves a simple, yet beautiful UI generating promotion codes using a pool. The program may setup a second private pool that requires a `Authorization` HTTP header. diff --git a/cmd/api/main.go b/cmd/api/main.go index 6af29bc..763c677 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -14,7 +14,7 @@ const ( ) func handle(w http.ResponseWriter, _ *http.Request) { - code, err := burgoking.GenerateCode(nil) + code, err := burgoking.GenerateCodeStatic(nil) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return diff --git a/cmd/code/main.go b/cmd/code/main.go index ed3b989..1de3af1 100644 --- a/cmd/code/main.go +++ b/cmd/code/main.go @@ -14,7 +14,7 @@ var ( ) func generateCode() { - code, err := burgoking.GenerateCode(nil) + code, err := burgoking.GenerateCodeStatic(nil) if err != nil { _, _ = fmt.Fprintln(os.Stderr, "error:", err) os.Exit(1) diff --git a/cmd/web/main.go b/cmd/web/main.go index d1e1aba..430b77f 100644 --- a/cmd/web/main.go +++ b/cmd/web/main.go @@ -5,7 +5,6 @@ import ( "log" "net/http" "strconv" - "time" "github.com/scotow/burgoking" "github.com/sirupsen/logrus" @@ -20,59 +19,14 @@ var ( var ( port = flag.Int("p", 8080, "listening port") contact = flag.String("c", "", "contact address on error") - - publicSize = flag.Int("n", 3, "public code pool size") - publicExpiration = flag.Duration("d", 24*time.Hour, "public code expiration") - publicRetry = flag.Duration("r", 30*time.Second, "public code regeneration interval") - - privateDirectKey = flag.String("k", "", "authorization token for private and direct code (disable if empty)") - privateSize = flag.Int("N", 1, "private code pool size") - privateExpiration = flag.Duration("D", 24*time.Hour, "private code expiration") - privateRetry = flag.Duration("R", 30*time.Second, "private code regeneration interval") ) -func handleCodeRequest(p *burgoking.Pool, t string, w http.ResponseWriter, r *http.Request) { - codeC, cancelC := make(chan string), make(chan struct{}) - go p.GetCode(codeC, cancelC) - - select { - case code := <-codeC: - w.WriteHeader(http.StatusOK) - w.Header().Set("Content-Type", "text/plain") - _, _ = w.Write([]byte(code)) - logrus.WithFields(logrus.Fields{"code": code, "ip": realip.FromRequest(r), "type": t}).Info("Code used by user.") - case <-r.Context().Done(): - cancelC <- struct{}{} - } -} - -func handlePublic(w http.ResponseWriter, r *http.Request) { - handleCodeRequest(publicPool, "public", w, r) -} - -func handlePrivate(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("Authorization") != *privateDirectKey { - http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) - return - } - - handleCodeRequest(privatePool, "private", w, r) -} - -func handleDirect(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("Authorization") != *privateDirectKey { - http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) - return - } - - code, err := burgoking.GenerateCode(nil) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - +func handleCode(w http.ResponseWriter, r *http.Request) { + code, _ := burgoking.GenerateCodeStatic(nil) + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "text/plain") _, _ = w.Write([]byte(code)) - logrus.WithFields(logrus.Fields{"code": code, "ip": r.RemoteAddr, "type": "direct"}).Info("Code used by user.") + logrus.WithFields(logrus.Fields{"code": code, "ip": realip.FromRequest(r)}).Info("Code used by user.") } func handleContact(w http.ResponseWriter, _ *http.Request) { @@ -84,31 +38,7 @@ func main() { // Static files. http.Handle("/", http.FileServer(http.Dir("static"))) - - // Public pool. - pool, err := burgoking.NewPool(*publicSize, *publicExpiration, *publicRetry) - if err != nil { - logrus.Fatal(err) - return - } - publicPool = pool - - http.HandleFunc("/code", handlePublic) - - // Private pool. - if *privateDirectKey != "" { - pool, err = burgoking.NewPool(*privateSize, *privateExpiration, *privateRetry) - if err != nil { - logrus.Fatal(err) - return - } - privatePool = pool - - http.HandleFunc("/private", handlePrivate) - http.HandleFunc("/direct", handleDirect) - - logrus.Info("Private and direct code generation activated.") - } + http.HandleFunc("/code", handleCode) // Contact address. if *contact != "" { diff --git a/cmd/web_original/Dockerfile b/cmd/web_original/Dockerfile new file mode 100644 index 0000000..67a2d88 --- /dev/null +++ b/cmd/web_original/Dockerfile @@ -0,0 +1,38 @@ +################################## +# STEP 1 build executable binary # +################################## +FROM golang:alpine AS builder + +# Install git. +# Git is required for fetching the dependencies. +RUN apk update && apk add --no-cache git +COPY . $GOPATH/src/github.com/scotow/burgoking + +# Move to command directory. +WORKDIR $GOPATH/src/github.com/scotow/burgoking/cmd/web_original + +# Fetch dependencies. +# Using go get. +RUN go get -d -v + +# Build the binary. +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /go/bin/burgoking + +############################## +# STEP 2 build a small image # +############################## +FROM scratch + +# Copy our static executable and static files. +COPY --from=builder /go/bin/burgoking /burgoking +COPY cmd/web_original/static /static + +# Copy SSL certificates for HTTPS connections. +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ + +# Copy locale data. +COPY --from=builder /usr/local/go/lib/time/zoneinfo.zip / +ENV ZONEINFO=/zoneinfo.zip + +# Run the hello binary. +ENTRYPOINT ["/burgoking"] \ No newline at end of file diff --git a/cmd/web_original/main.go b/cmd/web_original/main.go new file mode 100644 index 0000000..d1e1aba --- /dev/null +++ b/cmd/web_original/main.go @@ -0,0 +1,119 @@ +package main + +import ( + "flag" + "log" + "net/http" + "strconv" + "time" + + "github.com/scotow/burgoking" + "github.com/sirupsen/logrus" + "github.com/tomasen/realip" +) + +var ( + publicPool *burgoking.Pool + privatePool *burgoking.Pool +) + +var ( + port = flag.Int("p", 8080, "listening port") + contact = flag.String("c", "", "contact address on error") + + publicSize = flag.Int("n", 3, "public code pool size") + publicExpiration = flag.Duration("d", 24*time.Hour, "public code expiration") + publicRetry = flag.Duration("r", 30*time.Second, "public code regeneration interval") + + privateDirectKey = flag.String("k", "", "authorization token for private and direct code (disable if empty)") + privateSize = flag.Int("N", 1, "private code pool size") + privateExpiration = flag.Duration("D", 24*time.Hour, "private code expiration") + privateRetry = flag.Duration("R", 30*time.Second, "private code regeneration interval") +) + +func handleCodeRequest(p *burgoking.Pool, t string, w http.ResponseWriter, r *http.Request) { + codeC, cancelC := make(chan string), make(chan struct{}) + go p.GetCode(codeC, cancelC) + + select { + case code := <-codeC: + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "text/plain") + _, _ = w.Write([]byte(code)) + logrus.WithFields(logrus.Fields{"code": code, "ip": realip.FromRequest(r), "type": t}).Info("Code used by user.") + case <-r.Context().Done(): + cancelC <- struct{}{} + } +} + +func handlePublic(w http.ResponseWriter, r *http.Request) { + handleCodeRequest(publicPool, "public", w, r) +} + +func handlePrivate(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("Authorization") != *privateDirectKey { + http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) + return + } + + handleCodeRequest(privatePool, "private", w, r) +} + +func handleDirect(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("Authorization") != *privateDirectKey { + http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden) + return + } + + code, err := burgoking.GenerateCode(nil) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + _, _ = w.Write([]byte(code)) + logrus.WithFields(logrus.Fields{"code": code, "ip": r.RemoteAddr, "type": "direct"}).Info("Code used by user.") +} + +func handleContact(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte(*contact)) +} + +func main() { + flag.Parse() + + // Static files. + http.Handle("/", http.FileServer(http.Dir("static"))) + + // Public pool. + pool, err := burgoking.NewPool(*publicSize, *publicExpiration, *publicRetry) + if err != nil { + logrus.Fatal(err) + return + } + publicPool = pool + + http.HandleFunc("/code", handlePublic) + + // Private pool. + if *privateDirectKey != "" { + pool, err = burgoking.NewPool(*privateSize, *privateExpiration, *privateRetry) + if err != nil { + logrus.Fatal(err) + return + } + privatePool = pool + + http.HandleFunc("/private", handlePrivate) + http.HandleFunc("/direct", handleDirect) + + logrus.Info("Private and direct code generation activated.") + } + + // Contact address. + if *contact != "" { + http.HandleFunc("/contact", handleContact) + } + + log.Fatal(http.ListenAndServe(":"+strconv.Itoa(*port), nil)) +} diff --git a/cmd/web_original/static/browserconfig.xml b/cmd/web_original/static/browserconfig.xml new file mode 100644 index 0000000..6a1d00e --- /dev/null +++ b/cmd/web_original/static/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #da532c + + + diff --git a/cmd/web_original/static/css/styles.css b/cmd/web_original/static/css/styles.css new file mode 100644 index 0000000..ca093bf --- /dev/null +++ b/cmd/web_original/static/css/styles.css @@ -0,0 +1,52 @@ +@import url(https://fonts.googleapis.com/css?family=Lobster); + +html, body { + height: 100%; +} + +html { + font-size: 0; + font-family: 'Lobster', sans-serif; + color: #fff; + background: #131313; +} + +body { + margin: 0; + padding: 0; + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +body::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 4px; + background: #15b154; +} + +.video { + flex: 0; + width: 80vw; + max-width: 300px; + border-radius: 35%; +} + +.label { + margin-top: 20px; + text-align: center; + color: white; + font-size: 36px; + font-family: 'Lobster', sans-serif; +} + +.label.done { + margin-top: 18px; + font-size: 44px; +} diff --git a/cmd/web_original/static/favicon.ico b/cmd/web_original/static/favicon.ico new file mode 100644 index 0000000..129338e Binary files /dev/null and b/cmd/web_original/static/favicon.ico differ diff --git a/cmd/web_original/static/icons/android-chrome-192x192.png b/cmd/web_original/static/icons/android-chrome-192x192.png new file mode 100644 index 0000000..c3147ae Binary files /dev/null and b/cmd/web_original/static/icons/android-chrome-192x192.png differ diff --git a/cmd/web_original/static/icons/android-chrome-256x256.png b/cmd/web_original/static/icons/android-chrome-256x256.png new file mode 100644 index 0000000..3aa9115 Binary files /dev/null and b/cmd/web_original/static/icons/android-chrome-256x256.png differ diff --git a/cmd/web_original/static/icons/apple-touch-icon.png b/cmd/web_original/static/icons/apple-touch-icon.png new file mode 100644 index 0000000..d280c8d Binary files /dev/null and b/cmd/web_original/static/icons/apple-touch-icon.png differ diff --git a/cmd/web_original/static/icons/favicon-16x16.png b/cmd/web_original/static/icons/favicon-16x16.png new file mode 100644 index 0000000..363656d Binary files /dev/null and b/cmd/web_original/static/icons/favicon-16x16.png differ diff --git a/cmd/web_original/static/icons/favicon-32x32.png b/cmd/web_original/static/icons/favicon-32x32.png new file mode 100644 index 0000000..10796bc Binary files /dev/null and b/cmd/web_original/static/icons/favicon-32x32.png differ diff --git a/cmd/web_original/static/icons/mstile-150x150.png b/cmd/web_original/static/icons/mstile-150x150.png new file mode 100644 index 0000000..0d5f76f Binary files /dev/null and b/cmd/web_original/static/icons/mstile-150x150.png differ diff --git a/cmd/web_original/static/icons/safari-pinned-tab.svg b/cmd/web_original/static/icons/safari-pinned-tab.svg new file mode 100644 index 0000000..0fcaf83 --- /dev/null +++ b/cmd/web_original/static/icons/safari-pinned-tab.svg @@ -0,0 +1,22 @@ + + + + +Created by potrace 1.11, written by Peter Selinger 2001-2013 + + + + + diff --git a/cmd/web_original/static/icons/site.webmanifest b/cmd/web_original/static/icons/site.webmanifest new file mode 100644 index 0000000..650a363 --- /dev/null +++ b/cmd/web_original/static/icons/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/icons/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/icons/android-chrome-256x256.png", + "sizes": "256x256", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/cmd/web_original/static/images/burger.mp4 b/cmd/web_original/static/images/burger.mp4 new file mode 100644 index 0000000..84df373 Binary files /dev/null and b/cmd/web_original/static/images/burger.mp4 differ diff --git a/cmd/web_original/static/index.html b/cmd/web_original/static/index.html new file mode 100644 index 0000000..5353da5 --- /dev/null +++ b/cmd/web_original/static/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + Burger King Code Generator + + + +
Waiting for connection
+ + \ No newline at end of file diff --git a/cmd/web_original/static/js/app.js b/cmd/web_original/static/js/app.js new file mode 100644 index 0000000..65a1e35 --- /dev/null +++ b/cmd/web_original/static/js/app.js @@ -0,0 +1,41 @@ +document.addEventListener('DOMContentLoaded', function() { + function setLabel(text) { + document.getElementById('label').innerText = text; + } + + function sendRequest(method, url, onLoad, onSuccess, onError) { + const request = new XMLHttpRequest(); + + request.onreadystatechange = function() { + if (request.readyState === XMLHttpRequest.DONE) { + if (request.status === 200) { + if (onSuccess) onSuccess(request); + } else { + if (onError) onError(request); + } + } else if (request.readyState === XMLHttpRequest.OPENED) { + if (onLoad) onLoad(); + } + }; + request.onerror = onError; + + request.open(method, url, true); + request.send(null); + } + + function errorOccurred() { + sendRequest('GET', '/contact', null, function (request) { + setLabel('An error has occurred. Feel free to send me an email at \'' + request.responseText + '\' to help me improve this project.'); + }, function () { + setLabel('An error has occurred. Feel free to send an email to the admin of this website.'); + }); + } + + sendRequest('GET', '/code', function() { + setLabel('Loading your Burger'); + }, function (request) { + setLabel(request.responseText); + document.title = request.responseText; + new Audio('/sounds/burger.m4a').play(); + }, errorOccurred); +}); \ No newline at end of file diff --git a/cmd/web_original/static/sounds/burger.m4a b/cmd/web_original/static/sounds/burger.m4a new file mode 100644 index 0000000..ae8744c Binary files /dev/null and b/cmd/web_original/static/sounds/burger.m4a differ