web/barf/frontend: init
1
go.work
|
@ -3,4 +3,5 @@ go 1.21.7
|
|||
use (
|
||||
./go
|
||||
./web/barf/sapi
|
||||
./web/barf/frontend
|
||||
)
|
||||
|
|
|
@ -6,4 +6,5 @@
|
|||
|
||||
{
|
||||
sapi = import ./sapi args;
|
||||
frontend = import ./frontend args;
|
||||
}
|
||||
|
|
11
web/barf/frontend/default.nix
Normal file
|
@ -0,0 +1,11 @@
|
|||
# SPDX-FileCopyrightText: 2024 Luke Granger-Brown <depot@lukegb.com>
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
{ pkgs, lib, ... }:
|
||||
|
||||
pkgs.buildGoModule {
|
||||
name = "barf-fe";
|
||||
src = lib.sourceByRegex ./. [".*\.go$" "go.mod" "go.sum" "static" "static/clipit" ".*/.*\.webm" ".*/.*\.png" ".*/.*\.wav" ".*/.*\.svg" ".*\.html"];
|
||||
vendorHash = "sha256:0w1k1ykga70af3643lky701kf27pfmgc3lhznfq1v32ww365w57f";
|
||||
}
|
173
web/barf/frontend/frontend.go
Normal file
|
@ -0,0 +1,173 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
var (
|
||||
serve = flag.String("serve", ":11111", "Port number.")
|
||||
samBackend = flag.String("sam_backend", "http://localhost:11316", "Sam backend.")
|
||||
dbPath = flag.String("db_path", "./db.db", "Path to database.")
|
||||
)
|
||||
|
||||
//go:embed index.html
|
||||
var indexTmplBytes []byte
|
||||
|
||||
//go:embed static
|
||||
var staticFS embed.FS
|
||||
|
||||
type application struct {
|
||||
db *sql.DB
|
||||
indexTmpl *template.Template
|
||||
}
|
||||
|
||||
type dataset struct {
|
||||
CurrentPhase int `json:"currentPhase"`
|
||||
CurrentPhaseDialog int `json:"currentPhaseDialog"`
|
||||
AudioEnabled bool `json:"audioEnabled"`
|
||||
Name string `json:"name"`
|
||||
DiscordUsername string `json:"discordUsername"`
|
||||
ReceiveEmail bool `json:"receiveEmail"`
|
||||
Email string `json:"email"`
|
||||
DateSat31August string `json:"dateSat31August"`
|
||||
DateSat7September string `json:"dateSat7September"`
|
||||
ActivityEscapeRoom string `json:"activityEscapeRoom"`
|
||||
ActivityPub string `json:"activityPub"`
|
||||
ActivityMeanGirlsTheMusical string `json:"activityMeanGirlsTheMusical"`
|
||||
ActivityKaraoke string `json:"activityKaraoke"`
|
||||
AccommodationRequired bool `json:"accommodationRequired"`
|
||||
TravelCosts bool `json:"travelCosts"`
|
||||
Misc string `json:"misc"`
|
||||
}
|
||||
|
||||
func (a *application) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" {
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(rw, "Sorry, you need a key to access this application.\n")
|
||||
return
|
||||
}
|
||||
|
||||
key := r.URL.Path[1:]
|
||||
if r.Method == "PUT" {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
log.Printf("failed to read PUT body: %v", err)
|
||||
http.Error(rw, "reading body failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var response dataset
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
log.Printf("failed to unmarshal PUT dataset: %v", err)
|
||||
http.Error(rw, "unmarshalling body failed", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
cleanedBody, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
log.Printf("failed to remarshal PUT dataset: %v", err)
|
||||
http.Error(rw, "remarshalling body failed", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
res, err := a.db.ExecContext(r.Context(), `UPDATE responses SET responses = ? WHERE key = ?`, string(cleanedBody), key)
|
||||
if err != nil {
|
||||
log.Printf("failed to update DB: %v", err)
|
||||
http.Error(rw, "updating database failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
rowCount, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
log.Printf("determining affected rows: %v", err)
|
||||
http.Error(rw, "updating database failed", http.StatusInternalServerError)
|
||||
return
|
||||
} else if rowCount == 0 {
|
||||
log.Printf("no rows affected for key %v", key)
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(rw, "Sorry, you need a key to access this application.\n")
|
||||
return
|
||||
}
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
var responseJSON *string
|
||||
err := a.db.QueryRowContext(r.Context(), `select responses from responses where key = ?`, key).Scan(&responseJSON)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(rw, "Sorry, you need a key to access this application.\n")
|
||||
return
|
||||
} else if err != nil {
|
||||
log.Printf("getting responses: %v", err)
|
||||
http.Error(rw, "internal error getting responses", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
var responseJSONStr string
|
||||
if responseJSON == nil {
|
||||
responseJSONStr = "{}"
|
||||
} else {
|
||||
var response dataset
|
||||
if err := json.Unmarshal([]byte(*responseJSON), &response); err != nil {
|
||||
log.Printf("database key %v corrupt, not valid json?: %v", key, err)
|
||||
http.Error(rw, "response data corrupt!", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if response.CurrentPhase >= 5 {
|
||||
// Make sure people who have saved _completion_ start at the beginning.
|
||||
response.CurrentPhase = 0
|
||||
response.CurrentPhaseDialog = 0
|
||||
}
|
||||
responseJSONBytes, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
log.Printf("couldn't reserialise database key %v: %v", key, err)
|
||||
http.Error(rw, "something went wrong with reserialisation...", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
responseJSONStr = string(responseJSONBytes)
|
||||
}
|
||||
|
||||
rw.Header().Set("Content-type", "text/html")
|
||||
a.indexTmpl.Execute(rw, template.JS(responseJSONStr))
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
indexTmpl, err := template.New("index").Parse(string(indexTmplBytes))
|
||||
if err != nil {
|
||||
log.Fatalf("parsing index template: %w", err)
|
||||
}
|
||||
|
||||
db, err := sql.Open("sqlite3", *dbPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
app := &application{db: db, indexTmpl: indexTmpl}
|
||||
|
||||
http.Handle("/", app)
|
||||
|
||||
http.Handle("/static/", http.FileServer(http.FS(staticFS)))
|
||||
|
||||
samBackendPath, err := url.Parse(*samBackend)
|
||||
if err != nil {
|
||||
log.Fatalf("parsing -sam_backend=%q: %w", *samBackend, err)
|
||||
}
|
||||
be := httputil.NewSingleHostReverseProxy(samBackendPath)
|
||||
http.Handle("/sam", be)
|
||||
|
||||
log.Printf("serving on %v", *serve)
|
||||
log.Fatal(http.ListenAndServe(*serve, nil))
|
||||
}
|
5
web/barf/frontend/go.mod
Normal file
|
@ -0,0 +1,5 @@
|
|||
module hg.lukegb.com/lukegb/depot/web/barf/frontend
|
||||
|
||||
go 1.21.7
|
||||
|
||||
require github.com/mattn/go-sqlite3 v1.14.22
|
2
web/barf/frontend/go.sum
Normal file
|
@ -0,0 +1,2 @@
|
|||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
1435
web/barf/frontend/index.html
Normal file
BIN
web/barf/frontend/static/agenticon.png
Normal file
After Width: | Height: | Size: 679 B |
BIN
web/barf/frontend/static/audioenabled.wav
Normal file
BIN
web/barf/frontend/static/calendaricon.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
17
web/barf/frontend/static/check.svg
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="32"
|
||||
height="32"
|
||||
viewBox="0 0 32 32"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs1" /><g
|
||||
id="layer1"><path
|
||||
style="fill:#000000;stroke-width:3.62827"
|
||||
d="m 20.503931,19.512586 -9.514108,9.29028 -4.1834316,-4.622637 -4.1834312,-4.622641 v -4.162206 -4.16221 H 4.6153319 6.6077 l 2.9721359,2.972136 2.9721371,2.972136 7.001463,-6.707827 7.001464,-6.7078276 h 1.731571 1.731572 v 3.2302586 3.230259 z"
|
||||
id="path1" /></g></svg>
|
After Width: | Height: | Size: 690 B |
BIN
web/barf/frontend/static/clipit/goodbye.webm
Normal file
BIN
web/barf/frontend/static/clipit/headphones.webm
Normal file
BIN
web/barf/frontend/static/clipit/intro.webm
Normal file
BIN
web/barf/frontend/static/clipit/sleepy.webm
Normal file
BIN
web/barf/frontend/static/clipit/tapscreen.webm
Normal file
BIN
web/barf/frontend/static/clipit/writing.webm
Normal file
BIN
web/barf/frontend/static/emailicon.png
Normal file
After Width: | Height: | Size: 725 B |
BIN
web/barf/frontend/static/erroricon.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
web/barf/frontend/static/filmstripicon.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
web/barf/frontend/static/networkicon.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
web/barf/frontend/static/soundicon.png
Normal file
After Width: | Height: | Size: 582 B |
BIN
web/barf/frontend/static/speechicon.png
Normal file
After Width: | Height: | Size: 2.4 KiB |