wordsearch/cloudfn/cloudfn.go

142 lines
3.7 KiB
Go
Raw Normal View History

2023-10-08 14:25:57 +01:00
package cloudfn
import (
"bufio"
"context"
"fmt"
"log"
"net/http"
"os"
"sort"
"text/template"
"cloud.google.com/go/storage"
"github.com/GoogleCloudPlatform/functions-framework-go/functions"
"github.com/ray1729/puzzle-solver/anagram"
"github.com/ray1729/puzzle-solver/match"
"github.com/rs/cors"
)
var anagramDB anagram.DB
var matchDB match.DB
func initializeDB(ctx context.Context, bucketName, objectName string) error {
client, err := storage.NewClient(ctx)
if err != nil {
return fmt.Errorf("error creating storage client: %v", err)
}
r, err := client.Bucket(bucketName).Object(objectName).NewReader(ctx)
if err != nil {
return fmt.Errorf("error opening gs://%s/%s: %v", bucketName, objectName, err)
}
defer r.Close()
anagramDB = anagram.New()
matchDB = match.New()
sc := bufio.NewScanner(r)
for sc.Scan() {
s := sc.Text()
anagramDB.Add(s)
matchDB.Add(s)
}
if err := sc.Err(); err != nil {
return fmt.Errorf("error reading gs://%s/%s: %v", bucketName, objectName, err)
}
return nil
}
func init() {
ctx := context.Background()
bucketName := mustGetenv("WORDLIST_BUCKET")
objectName := mustGetenv("WORDLIST_PATH")
log.Println("Initializing databases")
if err := initializeDB(ctx, bucketName, objectName); err != nil {
panic(err)
}
var corsHandler = cors.Default()
log.Println("Registering HTTP function with the Functions Framework")
functions.HTTP("WordSearch", func(w http.ResponseWriter, r *http.Request) {
corsHandler.ServeHTTP(w, r, handleFormSubmission)
})
}
func mustGetenv(s string) string {
v := os.Getenv(s)
if len(v) == 0 {
panic(fmt.Sprintf("environment variable %s not set", s))
}
return v
}
func handleFormSubmission(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
log.Printf("error parsing form: %v", err)
http.Error(w, "error parsing form", http.StatusBadRequest)
return
}
mode := r.Form.Get("mode")
pattern := r.Form.Get("pattern")
if len(pattern) == 0 {
http.Error(w, "Missing pattern", http.StatusBadRequest)
return
}
switch mode {
case "match":
results := matchResults(matchDB, pattern)
renderTemplate(w, resultsTmpl, results)
case "anagrams":
results := anagramResults(anagramDB, pattern)
renderTemplate(w, resultsTmpl, results)
default:
log.Printf("invalid mode: %s", mode)
http.Error(w, fmt.Sprintf("Invalid mode: %s", mode), http.StatusBadRequest)
}
}
func anagramResults(db anagram.DB, pattern string) ResultParams {
var params ResultParams
params.Results = db.FindAnagrams(pattern)
if len(params.Results) > 0 {
params.Preamble = fmt.Sprintf("Anagrams of %q:", pattern)
} else {
params.Preamble = fmt.Sprintf("Found no anagrams of %q", pattern)
}
sort.Slice(params.Results, func(i, j int) bool { return params.Results[i] < params.Results[j] })
return params
}
func matchResults(db match.DB, pattern string) ResultParams {
var params ResultParams
params.Results = db.FindMatches(pattern)
if len(params.Results) > 0 {
params.Preamble = fmt.Sprintf("Matches for %q:", pattern)
} else {
params.Preamble = fmt.Sprintf("Found no matches for %q", pattern)
}
sort.Slice(params.Results, func(i, j int) bool { return params.Results[i] < params.Results[j] })
return params
}
func renderTemplate(w http.ResponseWriter, t *template.Template, params any) {
err := t.Execute(w, params)
if err != nil {
log.Printf("Error rendering template %s: %v", t.Name(), err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
}
}
type ResultParams struct {
Preamble string
Results []string
}
var resultsTmpl = template.Must(template.New("results").Parse(`
{{ with .Preamble }}
<p>{{ . }}</p>
{{ end }}
<ul>
{{ range .Results }}
<li>{{.}}</li>
{{ end }}
</ul>
`))