barf: some local updates I forgot to push
This commit is contained in:
parent
401b903cdb
commit
8ee5597278
9 changed files with 576 additions and 56 deletions
|
@ -8,10 +8,16 @@
|
|||
systemd.targets.barf = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
security.wrappers.barf-cli = {
|
||||
source = "${depot.web.barf.frontend}/bin/barfcli";
|
||||
setuid = true;
|
||||
owner = "root";
|
||||
group = "root";
|
||||
};
|
||||
systemd.services.barf-fe = {
|
||||
wantedBy = [ "barf.target" ];
|
||||
serviceConfig = {
|
||||
ExecStart = "${depot.web.barf.frontend}/bin/frontend -serve=:12001 -sam_backend=http://127.0.0.1:11316 -db_path=/var/lib/barf-fe/db.db";
|
||||
ExecStart = "${depot.web.barf.frontend}/bin/barffe -serve=:12001 -sam_backend=http://127.0.0.1:11316 -db_path=/var/lib/barf-fe/db.db";
|
||||
StateDirectory = "barf-fe";
|
||||
User = "barf-fe";
|
||||
PrivateTmp = true;
|
||||
|
|
133
web/barf/frontend/barfcli/barfcli.go
Normal file
133
web/barf/frontend/barfcli/barfcli.go
Normal file
|
@ -0,0 +1,133 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"hg.lukegb.com/lukegb/depot/web/barf/frontend/barfdb"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
var (
|
||||
db *barfdb.DB
|
||||
flagDBPath string
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "barfcli",
|
||||
Short: "BARF is the Birthday Activities Registration Form",
|
||||
Long: `Manage your Birthday Activities`,
|
||||
}
|
||||
|
||||
var initializeCmd = &cobra.Command{
|
||||
Use: "initialize",
|
||||
Short: "Initialize a new database",
|
||||
Args: cobra.ExactArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return db.Initialize(cmd.Context())
|
||||
},
|
||||
}
|
||||
|
||||
var addCmd = &cobra.Command{
|
||||
Use: "add keyprefix name",
|
||||
Short: "Add a new key",
|
||||
Args: cobra.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
key, err := db.GetOrCreateKey(ctx, args[0], &barfdb.Dataset{Name: args[1]})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(key)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var lsCmd = &cobra.Command{
|
||||
Use: "ls",
|
||||
Short: "List keys and respondents",
|
||||
Args: cobra.ExactArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
entries, err := db.List(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
maxLengths := []int{
|
||||
len("basekey"),
|
||||
len("key"),
|
||||
len("name"),
|
||||
}
|
||||
for _, entry := range entries {
|
||||
for n, l := range []int{
|
||||
len(entry.BaseKey()),
|
||||
len(entry.FullKey),
|
||||
len(entry.Dataset.Name),
|
||||
} {
|
||||
if maxLengths[n] < l {
|
||||
maxLengths[n] = l
|
||||
}
|
||||
}
|
||||
}
|
||||
fmtFmt := fmt.Sprintf("%%-%ds\t%%-%ds\t%%-%ds\t%%v\n", maxLengths[0], maxLengths[1], maxLengths[2])
|
||||
fmt.Printf(fmtFmt, "basekey", "key", "name", "completed")
|
||||
fmt.Printf(fmtFmt, strings.Repeat("-", maxLengths[0]), strings.Repeat("-", maxLengths[1]), strings.Repeat("-", maxLengths[2]), "---------")
|
||||
for _, entry := range entries {
|
||||
fmt.Printf(fmtFmt, entry.BaseKey(), entry.FullKey, entry.Dataset.Name, entry.Completed())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var getCmd = &cobra.Command{
|
||||
Use: "get short",
|
||||
Short: "Get responses for a user",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
resp, err := db.GetResponseForShortKey(ctx, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hs, err := resp.HumanString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(hs)
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func openDB() {
|
||||
sdb, err := sql.Open("sqlite3", flagDBPath)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Opening database %v: %v\n", flagDBPath, err)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
db = barfdb.New(sdb)
|
||||
}
|
||||
|
||||
func closeDB() {
|
||||
db.Close()
|
||||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(openDB)
|
||||
cobra.OnFinalize(closeDB)
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&flagDBPath, "db", "/var/lib/barf-fe/db.db", "BARF database.")
|
||||
|
||||
rootCmd.AddCommand(initializeCmd, addCmd, lsCmd, getCmd)
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
196
web/barf/frontend/barfdb/barfdb.go
Normal file
196
web/barf/frontend/barfdb/barfdb.go
Normal file
|
@ -0,0 +1,196 @@
|
|||
package barfdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"database/sql"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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 (ds *Dataset) Scan(value any) error {
|
||||
return json.Unmarshal([]byte(value.(string)), ds)
|
||||
}
|
||||
|
||||
func (ds *Dataset) Value() (driver.Value, error) {
|
||||
b, err := json.Marshal(ds)
|
||||
return string(b), err
|
||||
}
|
||||
|
||||
func (ds *Dataset) HumanString() (string, error) {
|
||||
bs, err := json.MarshalIndent(ds, "", " ")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(bs), nil
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func New(sdb *sql.DB) *DB {
|
||||
return &DB{sdb}
|
||||
}
|
||||
|
||||
func (d *DB) Close() error {
|
||||
return d.db.Close()
|
||||
}
|
||||
|
||||
var ErrNoSuchKey = errors.New("barfdb: no such key")
|
||||
|
||||
const (
|
||||
keyBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
keyLength = 12
|
||||
)
|
||||
|
||||
func (d *DB) Initialize(ctx context.Context) error {
|
||||
_, err := d.db.ExecContext(ctx, "CREATE TABLE responses (key STRING PRIMARY KEY NOT NULL, responses JSONB NOT NULL)")
|
||||
return err
|
||||
}
|
||||
|
||||
type Entry struct {
|
||||
FullKey string
|
||||
Dataset Dataset
|
||||
}
|
||||
|
||||
func (e Entry) BaseKey() string {
|
||||
if strings.Contains(e.FullKey, "/") {
|
||||
return e.FullKey[:strings.Index(e.FullKey, "/")]
|
||||
}
|
||||
return e.FullKey
|
||||
}
|
||||
|
||||
func (e Entry) Completed() bool {
|
||||
mustHaveValue := []string{
|
||||
e.Dataset.DiscordUsername,
|
||||
e.Dataset.Name,
|
||||
e.Dataset.ActivityEscapeRoom,
|
||||
e.Dataset.ActivityKaraoke,
|
||||
e.Dataset.ActivityMeanGirlsTheMusical,
|
||||
e.Dataset.ActivityPub,
|
||||
e.Dataset.DateSat31August,
|
||||
e.Dataset.DateSat7September,
|
||||
}
|
||||
for _, v := range mustHaveValue {
|
||||
if v == "" {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (d *DB) List(ctx context.Context) ([]Entry, error) {
|
||||
rows, err := d.db.QueryContext(ctx, "SELECT key, responses FROM responses ORDER BY key")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("querying database: %w", err)
|
||||
}
|
||||
var entries []Entry
|
||||
for rows.Next() {
|
||||
var (
|
||||
fullKey string
|
||||
dataset Dataset
|
||||
)
|
||||
if err := rows.Scan(&fullKey, &dataset); err != nil {
|
||||
return nil, fmt.Errorf("scanning row from database: %w", err)
|
||||
}
|
||||
|
||||
entry := Entry{
|
||||
FullKey: fullKey,
|
||||
Dataset: dataset,
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (d *DB) GetOrCreateKey(ctx context.Context, baseKey string, initialDataset *Dataset) (string, error) {
|
||||
var existingKey string
|
||||
err := d.db.QueryRowContext(ctx, `SELECT key FROM responses WHERE key LIKE ? || '/%'`, baseKey).Scan(&existingKey)
|
||||
if err == nil {
|
||||
return existingKey, nil
|
||||
} else if errors.Is(err, sql.ErrNoRows) {
|
||||
// This is fine, we'll create it.
|
||||
} else {
|
||||
return "", fmt.Errorf("querying database for existing keys for base %q: %w", baseKey, err)
|
||||
}
|
||||
|
||||
randomKeyBytes := make([]byte, keyLength)
|
||||
if _, err := rand.Read(randomKeyBytes); err != nil {
|
||||
return "", fmt.Errorf("generating random key: %w", err)
|
||||
}
|
||||
|
||||
randomKeyStringBytes := make([]byte, keyLength)
|
||||
for n, b := range randomKeyBytes {
|
||||
randomKeyStringBytes[n] = keyBytes[int(b)%len(keyBytes)]
|
||||
}
|
||||
randomKey := string(randomKeyStringBytes)
|
||||
fullKey := baseKey + "/" + randomKey
|
||||
|
||||
if _, err := d.db.ExecContext(ctx, `INSERT INTO responses (key, responses) VALUES (?, ?)`, fullKey, initialDataset); err != nil {
|
||||
return "", fmt.Errorf("inserting into responses table: %w", err)
|
||||
}
|
||||
return fullKey, nil
|
||||
}
|
||||
|
||||
func (d *DB) GetResponseForShortKey(ctx context.Context, shortKey string) (*Dataset, error) {
|
||||
var response Dataset
|
||||
err := d.db.QueryRowContext(ctx, `SELECT responses FROM responses WHERE key = ? OR key LIKE ? || '/%'`, shortKey, shortKey).Scan(&response)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrNoSuchKey
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("fetching %v from database: %w", shortKey, err)
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (d *DB) GetResponseForKey(ctx context.Context, key string) (*Dataset, error) {
|
||||
var response Dataset
|
||||
err := d.db.QueryRowContext(ctx, `SELECT responses FROM responses WHERE key = ?`, key).Scan(&response)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, ErrNoSuchKey
|
||||
} else if err != nil {
|
||||
return nil, fmt.Errorf("fetching %v from database: %w", key, err)
|
||||
}
|
||||
return &response, nil
|
||||
}
|
||||
|
||||
func (d *DB) SaveResponseForKey(ctx context.Context, key string, dataset *Dataset) error {
|
||||
res, err := d.db.ExecContext(ctx, `UPDATE responses SET responses = ? WHERE key = ?`, dataset, key)
|
||||
if err != nil {
|
||||
return fmt.Errorf("updating database for %v: %w", key, err)
|
||||
}
|
||||
rowCount, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("determining affected row count for %v: %w", key, err)
|
||||
} else if rowCount == 0 {
|
||||
return ErrNoSuchKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewDB(db *sql.DB) *DB {
|
||||
return &DB{db: db}
|
||||
}
|
166
web/barf/frontend/barfdb/barfdb_test.go
Normal file
166
web/barf/frontend/barfdb/barfdb_test.go
Normal file
|
@ -0,0 +1,166 @@
|
|||
package barfdb
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func makeDB(t *testing.T) *DB {
|
||||
t.Helper()
|
||||
sdb, err := sql.Open("sqlite3", ":memory:")
|
||||
if err != nil {
|
||||
t.Fatalf("creating inmemory sqlite3 database: %v", err)
|
||||
}
|
||||
t.Cleanup(func() { sdb.Close() })
|
||||
|
||||
db := &DB{sdb}
|
||||
if err := db.Initialize(context.Background()); err != nil {
|
||||
t.Fatalf("initializing DB: %v", err)
|
||||
}
|
||||
|
||||
return &DB{sdb}
|
||||
}
|
||||
|
||||
func TestGetOrCreateKey(t *testing.T) {
|
||||
db := makeDB(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
ds := Dataset{
|
||||
Name: "Slartibartfast",
|
||||
}
|
||||
|
||||
key, err := db.GetOrCreateKey(ctx, "testkey", &ds)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateKey (first time): %v", err)
|
||||
}
|
||||
|
||||
key2, err := db.GetOrCreateKey(ctx, "testkey", &ds)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateKey (second time): %v", err)
|
||||
}
|
||||
|
||||
if key != key2 {
|
||||
t.Errorf("GetOrCreateKey returned different results for 'testkey': %v != %v", key, key2)
|
||||
}
|
||||
|
||||
gotds, err := db.GetResponseForKey(ctx, key)
|
||||
if err != nil {
|
||||
t.Fatalf("GetResponseForKey(%q): %v", key, err)
|
||||
}
|
||||
if diff := cmp.Diff(*gotds, ds); diff != "" {
|
||||
t.Errorf("GetResponseForKey diff (-got +want):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveResponseForKey(t *testing.T) {
|
||||
db := makeDB(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
key, err := db.GetOrCreateKey(ctx, "testkey", &Dataset{})
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateKey: %v", err)
|
||||
}
|
||||
|
||||
ds := Dataset{
|
||||
Name: "Slartibartfast",
|
||||
}
|
||||
if err := db.SaveResponseForKey(ctx, key, &ds); err != nil {
|
||||
t.Fatalf("SaveResponseForKey: %v", err)
|
||||
}
|
||||
|
||||
gotds, err := db.GetResponseForKey(ctx, key)
|
||||
if err != nil {
|
||||
t.Fatalf("GetResponseForKey: %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(*gotds, ds); diff != "" {
|
||||
t.Errorf("GetResponseForKey diff (-got +want):\n%s", diff)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveResponseForKey_DoesNotExist(t *testing.T) {
|
||||
db := makeDB(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
ds := Dataset{
|
||||
Name: "Slartibartfast",
|
||||
}
|
||||
if err := db.SaveResponseForKey(ctx, "testkey/foo", &ds); err != ErrNoSuchKey {
|
||||
t.Fatalf("SaveResponseForKey: %v (want ErrNoSuchKey)", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetResponseForKey_DoesNotExist(t *testing.T) {
|
||||
db := makeDB(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
if _, err := db.GetResponseForKey(ctx, "testkey/foo"); err != ErrNoSuchKey {
|
||||
t.Fatalf("GetResponseForKey: %v (want ErrNoSuchKey)", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseKey(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
FullKey string
|
||||
Want string
|
||||
}{{
|
||||
FullKey: "foo",
|
||||
Want: "foo",
|
||||
}, {
|
||||
FullKey: "foo/bar",
|
||||
Want: "foo",
|
||||
}, {
|
||||
FullKey: "foo/bar/baz",
|
||||
Want: "foo",
|
||||
}} {
|
||||
e := Entry{FullKey: tc.FullKey}
|
||||
got := e.BaseKey()
|
||||
if got != tc.Want {
|
||||
t.Errorf("Entry{FullKey: %q}.BaseKey() = %q; want %q", tc.FullKey, got, tc.Want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
db := makeDB(t)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
keys := map[string]string{}
|
||||
datasets := map[string]*Dataset{}
|
||||
mustGetOrCreateKey := func(key string, ds *Dataset) {
|
||||
fullKey, err := db.GetOrCreateKey(ctx, key, ds)
|
||||
if err != nil {
|
||||
t.Fatalf("GetOrCreateKey(ctx, %q, ds): %v", key, err)
|
||||
}
|
||||
keys[key] = fullKey
|
||||
datasets[key] = ds
|
||||
}
|
||||
mustGetOrCreateKey("2-foo", &Dataset{Name: "A"})
|
||||
mustGetOrCreateKey("1-fully-complete", &Dataset{
|
||||
Name: "B Fully Complete",
|
||||
})
|
||||
want := []Entry{{
|
||||
FullKey: keys["1-fully-complete"],
|
||||
Dataset: *datasets["1-fully-complete"],
|
||||
}, {
|
||||
FullKey: keys["2-foo"],
|
||||
Dataset: *datasets["2-foo"],
|
||||
}}
|
||||
|
||||
es, err := db.List(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("List(ctx): %v", err)
|
||||
}
|
||||
if diff := cmp.Diff(es, want); diff != "" {
|
||||
t.Errorf("List(ctx): has diff (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
|
@ -6,6 +6,27 @@
|
|||
|
||||
pkgs.buildGoModule {
|
||||
name = "barf-fe";
|
||||
src = lib.sourceByRegex ./. [".*\.go$" "go.mod" "go.sum" "static" "static/clipit" "static/fonts" ".*/.*\.webm" ".*/.*\.png" ".*/.*\.wav" ".*/.*\.svg" ".*\.html" ".*/.*\.woff" ".*/.*\.woff2" ".*/.*\.css"];
|
||||
vendorHash = "sha256:0w1k1ykga70af3643lky701kf27pfmgc3lhznfq1v32ww365w57f";
|
||||
src = lib.sourceByRegex ./. [
|
||||
".*\.go$"
|
||||
"go.mod"
|
||||
"go.sum"
|
||||
"barfdb"
|
||||
"barfcli"
|
||||
"static"
|
||||
"static/clipit"
|
||||
"static/fonts"
|
||||
".*/.*\.webm"
|
||||
".*/.*\.png"
|
||||
".*/.*\.wav"
|
||||
".*/.*\.svg"
|
||||
".*\.html"
|
||||
".*/.*\.woff"
|
||||
".*/.*\.woff2"
|
||||
".*/.*\.css"
|
||||
];
|
||||
vendorHash = "sha256:0caif1kkxycdqpcp593y6dimwpwh7qcngncv360qb7m9a3ikh82y";
|
||||
|
||||
postInstall = ''
|
||||
mv $out/bin/frontend $out/bin/barffe
|
||||
'';
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@ import (
|
|||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
"hg.lukegb.com/lukegb/depot/web/barf/frontend/barfdb"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
|
@ -30,7 +32,7 @@ var indexTmplBytes []byte
|
|||
var staticFS embed.FS
|
||||
|
||||
type application struct {
|
||||
db *sql.DB
|
||||
db *barfdb.DB
|
||||
indexTmpl *template.Template
|
||||
}
|
||||
|
||||
|
@ -68,42 +70,27 @@ func (a *application) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||
http.Error(rw, "reading body failed", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
var response dataset
|
||||
var response barfdb.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)
|
||||
if err := a.db.SaveResponseForKey(r.Context(), key, &response); errors.Is(err, barfdb.ErrNoSuchKey) {
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(rw, "Sorry, you need a key to access this application.\n")
|
||||
return
|
||||
} else if err != nil {
|
||||
log.Printf("SaveResponseKeyFor %v: %v", key, err)
|
||||
http.Error(rw, "saving response failed", http.StatusInternalServerError)
|
||||
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) {
|
||||
response, err := a.db.GetResponseForKey(r.Context(), key)
|
||||
if errors.Is(err, barfdb.ErrNoSuchKey) {
|
||||
rw.WriteHeader(http.StatusNotFound)
|
||||
fmt.Fprintf(rw, "Sorry, you need a key to access this application.\n")
|
||||
return
|
||||
|
@ -113,29 +100,18 @@ func (a *application) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
|||
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 >= 6 {
|
||||
// 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)
|
||||
if response.CurrentPhase >= 6 {
|
||||
// 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))
|
||||
|
@ -149,11 +125,12 @@ func main() {
|
|||
log.Fatalf("parsing index template: %v", err)
|
||||
}
|
||||
|
||||
db, err := sql.Open("sqlite3", *dbPath)
|
||||
sdb, err := sql.Open("sqlite3", *dbPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
defer sdb.Close()
|
||||
db := barfdb.New(sdb)
|
||||
|
||||
app := &application{db: db, indexTmpl: indexTmpl}
|
||||
|
||||
|
@ -163,7 +140,7 @@ func main() {
|
|||
|
||||
samBackendPath, err := url.Parse(*samBackend)
|
||||
if err != nil {
|
||||
log.Fatalf("parsing -sam_backend=%q: %w", *samBackend, err)
|
||||
log.Fatalf("parsing -sam_backend=%q: %v", *samBackend, err)
|
||||
}
|
||||
be := httputil.NewSingleHostReverseProxy(samBackendPath)
|
||||
http.Handle("/sam", be)
|
||||
|
|
|
@ -2,4 +2,13 @@ module hg.lukegb.com/lukegb/depot/web/barf/frontend
|
|||
|
||||
go 1.21.7
|
||||
|
||||
require github.com/mattn/go-sqlite3 v1.14.22
|
||||
require (
|
||||
github.com/google/go-cmp v0.6.0
|
||||
github.com/mattn/go-sqlite3 v1.14.22
|
||||
github.com/spf13/cobra v1.8.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
)
|
||||
|
|
|
@ -1,2 +1,14 @@
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
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=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
|
@ -14,7 +14,7 @@ html, body, #page {
|
|||
}
|
||||
body {
|
||||
background-color: #003399;
|
||||
font-family: Tahoma;
|
||||
font-family: 'Tahoma';
|
||||
|
||||
user-select: none;
|
||||
}
|
||||
|
@ -373,7 +373,7 @@ body.all-done .now-safe {
|
|||
}
|
||||
.dialog-btn {
|
||||
box-sizing: border-box;
|
||||
font-family: Tahoma;
|
||||
font-family: 'Tahoma';
|
||||
padding: 0.2em 1.8em;
|
||||
|
||||
background-color: #d8d0cc;
|
||||
|
@ -398,7 +398,7 @@ body.all-done .now-safe {
|
|||
width: 100%;
|
||||
min-height: 6rem;
|
||||
resize: vertical;
|
||||
font-family: Tahoma;
|
||||
font-family: 'Tahoma';
|
||||
}
|
||||
.if-on-mobile {
|
||||
display: none;
|
||||
|
|
Loading…
Reference in a new issue