Get rid of surrogate keys
This commit is contained in:
parent
0ff6402c34
commit
62a717c806
@ -3,7 +3,6 @@ package back
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
@ -72,29 +71,24 @@ func (s *Store) Close() error {
|
||||
|
||||
const schema = `
|
||||
CREATE TABLE event (
|
||||
id INTEGER PRIMARY KEY,
|
||||
alpha_id TEXT NOT NULL,
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
admin_code TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
earliest_date DATE NOT NULL,
|
||||
latest_date DATE NOT NULL,
|
||||
|
||||
UNIQUE (alpha_id)
|
||||
latest_date DATE NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE response (
|
||||
id INTEGER PRIMARY KEY,
|
||||
alpha_id TEXT NOT NULL,
|
||||
event_id INTEGER NOT NULL,
|
||||
id TEXT NOT NULL PRIMARY KEY,
|
||||
event_id TEXT NOT NULL,
|
||||
guest_name TEXT NOT NULL,
|
||||
|
||||
UNIQUE (alpha_id),
|
||||
FOREIGN KEY (event_id) REFERENCES event(id)
|
||||
);
|
||||
|
||||
CREATE TABLE response_time (
|
||||
response_id INTEGER NOT NULL,
|
||||
response_id TEXT NOT NULL,
|
||||
date DATE NOT NULL,
|
||||
time INTEGER NOT NULL,
|
||||
|
||||
@ -128,19 +122,19 @@ type CreateEventCommand struct {
|
||||
}
|
||||
|
||||
type CreateEventResult struct {
|
||||
AlphaID, AdminCode string
|
||||
EventID, AdminCode string
|
||||
}
|
||||
|
||||
const dbDateLayout = "2006-01-02"
|
||||
|
||||
func (s *Store) CreateEvent(ctx context.Context, cmd CreateEventCommand) (result CreateEventResult, err error) {
|
||||
const alphaIDLength = 10
|
||||
const idLength = 10
|
||||
const adminCodeLength = 10
|
||||
|
||||
conn := s.pool.Get(ctx)
|
||||
defer s.pool.Put(conn)
|
||||
|
||||
alphaID, err := s.genString(alphaIDLength)
|
||||
id, err := s.genString(idLength)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -151,10 +145,10 @@ func (s *Store) CreateEvent(ctx context.Context, cmd CreateEventCommand) (result
|
||||
}
|
||||
|
||||
const query = `
|
||||
INSERT INTO event(alpha_id, admin_code, name, description, earliest_date, latest_date)
|
||||
INSERT INTO event(id, admin_code, name, description, earliest_date, latest_date)
|
||||
VALUES (?, ?, ?, ?, ?, ?);`
|
||||
err = sqlitex.Exec(conn, query, nil,
|
||||
alphaID,
|
||||
id,
|
||||
adminCode,
|
||||
cmd.Name,
|
||||
cmd.Description,
|
||||
@ -166,12 +160,12 @@ func (s *Store) CreateEvent(ctx context.Context, cmd CreateEventCommand) (result
|
||||
}
|
||||
|
||||
result.AdminCode = adminCode
|
||||
result.AlphaID = alphaID
|
||||
result.EventID = id
|
||||
return
|
||||
}
|
||||
|
||||
type GetEventMetadataQuery struct {
|
||||
AlphaID string
|
||||
EventID string
|
||||
}
|
||||
|
||||
type GetEventMetadataResult struct {
|
||||
@ -187,7 +181,7 @@ func (s *Store) GetEventMetadata(ctx context.Context, query GetEventMetadataQuer
|
||||
const dbQuery = `
|
||||
SELECT name, description, earliest_date, latest_date
|
||||
FROM event
|
||||
WHERE alpha_id = ?;`
|
||||
WHERE id = ?;`
|
||||
var result GetEventMetadataResult
|
||||
var found bool
|
||||
err := sqlitex.Exec(conn, dbQuery,
|
||||
@ -206,7 +200,7 @@ func (s *Store) GetEventMetadata(ctx context.Context, query GetEventMetadataQuer
|
||||
}
|
||||
result.Latest, err = date.Parse(dbDateLayout, latestDateString)
|
||||
return err
|
||||
}, query.AlphaID)
|
||||
}, query.EventID)
|
||||
if err != nil {
|
||||
return GetEventMetadataResult{}, err
|
||||
}
|
||||
@ -217,7 +211,7 @@ func (s *Store) GetEventMetadata(ctx context.Context, query GetEventMetadataQuer
|
||||
}
|
||||
|
||||
type GetEventResponseSummaryQuery struct {
|
||||
AlphaID string
|
||||
EventID string
|
||||
}
|
||||
|
||||
type GetEventResponseSummaryResult struct {
|
||||
@ -229,18 +223,16 @@ func (s *Store) GetEventResponseSummary(ctx context.Context, query GetEventRespo
|
||||
defer s.pool.Put(conn)
|
||||
|
||||
var found bool
|
||||
var eventID int
|
||||
|
||||
const eventIDQuery = `
|
||||
SELECT id
|
||||
SELECT 1
|
||||
FROM event
|
||||
WHERE alpha_id = ?`
|
||||
WHERE id = ?`
|
||||
var err = sqlitex.Exec(conn, eventIDQuery,
|
||||
func(stmt *sqlite.Stmt) error {
|
||||
found = true
|
||||
eventID = stmt.ColumnInt(0)
|
||||
return nil
|
||||
}, query.AlphaID)
|
||||
}, query.EventID)
|
||||
if err != nil {
|
||||
return GetEventResponseSummaryResult{}, err
|
||||
}
|
||||
@ -257,7 +249,7 @@ func (s *Store) GetEventResponseSummary(ctx context.Context, query GetEventRespo
|
||||
func(stmt *sqlite.Stmt) error {
|
||||
result.TotalResponses = stmt.ColumnInt(0)
|
||||
return nil
|
||||
}, eventID)
|
||||
}, query.EventID)
|
||||
if err != nil {
|
||||
return GetEventResponseSummaryResult{}, err
|
||||
}
|
||||
@ -265,46 +257,37 @@ func (s *Store) GetEventResponseSummary(ctx context.Context, query GetEventRespo
|
||||
}
|
||||
|
||||
type CreateEventResponseCommand struct {
|
||||
EventAlphaID string
|
||||
EventID string
|
||||
GuestName string
|
||||
DateHours map[date.Date]map[int]struct{}
|
||||
}
|
||||
|
||||
type CreateEventResponseResult struct {
|
||||
ResponseAlphaID string
|
||||
ResponseID string
|
||||
}
|
||||
|
||||
func (s *Store) CreateEventResponse(ctx context.Context, cmd CreateEventResponseCommand) (result CreateEventResponseResult, err error) {
|
||||
const responseAlphaIDLength = 10
|
||||
const responseIDLength = 10
|
||||
|
||||
fmt.Printf("creating response: %+v\n", cmd)
|
||||
conn := s.pool.Get(ctx)
|
||||
defer s.pool.Put(conn)
|
||||
|
||||
defer sqlitex.Save(conn)(&err)
|
||||
responseAlphaID, err := s.genString(responseAlphaIDLength)
|
||||
responseID, err := s.genString(responseIDLength)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
const responseQuery = `
|
||||
INSERT INTO response(event_id, alpha_id, guest_name)
|
||||
SELECT event.id AS event_id, ? AS alpha_id, ? as guest_name
|
||||
FROM event
|
||||
WHERE event.alpha_id = ?;`
|
||||
err = sqlitex.Exec(conn, responseQuery, nil, responseAlphaID, cmd.GuestName, cmd.EventAlphaID)
|
||||
const responseQuery = `INSERT INTO response(event_id, id, guest_name) VALUES (?, ?, ?);`
|
||||
err = sqlitex.Exec(conn, responseQuery, nil, cmd.EventID, responseID, cmd.GuestName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
const responseTimeQuery = `
|
||||
INSERT INTO response_time(response_id, date, time)
|
||||
SELECT response.id AS response_id, ? AS date, ? AS time
|
||||
FROM response
|
||||
WHERE response.alpha_id = ?;`
|
||||
const responseTimeQuery = `INSERT INTO response_time(response_id, date, time) VALUES (?, ?, ?);`
|
||||
for d, hs := range cmd.DateHours {
|
||||
for h := range hs {
|
||||
err = sqlitex.Exec(conn, responseTimeQuery, nil, d.Format(dbDateLayout), h, responseAlphaID)
|
||||
err = sqlitex.Exec(conn, responseTimeQuery, nil, responseID, d.Format(dbDateLayout), h)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@ -312,13 +295,13 @@ func (s *Store) CreateEventResponse(ctx context.Context, cmd CreateEventResponse
|
||||
}
|
||||
|
||||
return CreateEventResponseResult{
|
||||
ResponseAlphaID: responseAlphaID,
|
||||
ResponseID: responseID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type GetEventResponseQuery struct {
|
||||
EventAlphaID string
|
||||
ResponseAlphaID string
|
||||
EventID string
|
||||
ResponseID string
|
||||
}
|
||||
|
||||
type GetEventResponseResult struct {
|
||||
@ -332,20 +315,17 @@ func (s *Store) GetEventResponse(ctx context.Context, query GetEventResponseQuer
|
||||
|
||||
var found bool
|
||||
var result GetEventResponseResult
|
||||
var responseID int
|
||||
|
||||
const responseQuery = `
|
||||
SELECT guest_name, response.id
|
||||
SELECT guest_name
|
||||
FROM response
|
||||
JOIN event ON response.event_id = event.id
|
||||
WHERE response.alpha_id = ? AND event.alpha_id = ?;`
|
||||
WHERE id = ? AND event_id = ?;`
|
||||
var err = sqlitex.Exec(conn, responseQuery,
|
||||
func(stmt *sqlite.Stmt) error {
|
||||
found = true
|
||||
result.GuestName = stmt.ColumnText(0)
|
||||
responseID = stmt.ColumnInt(1)
|
||||
return nil
|
||||
}, query.ResponseAlphaID, query.EventAlphaID)
|
||||
}, query.ResponseID, query.EventID)
|
||||
if err != nil {
|
||||
return GetEventResponseResult{}, err
|
||||
}
|
||||
@ -369,7 +349,7 @@ func (s *Store) GetEventResponse(ctx context.Context, query GetEventResponseQuer
|
||||
}
|
||||
result.DateHours[d][stmt.ColumnInt(1)] = struct{}{}
|
||||
return nil
|
||||
}, responseID)
|
||||
}, query.ResponseID)
|
||||
if err != nil {
|
||||
return GetEventResponseResult{}, err
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ func TestCreateEvent(t *testing.T) {
|
||||
is.NoErr(err)
|
||||
|
||||
metadataResult, err := store.GetEventMetadata(context.Background(), back.GetEventMetadataQuery{
|
||||
AlphaID: createResult.AlphaID,
|
||||
EventID: createResult.EventID,
|
||||
})
|
||||
is.NoErr(err)
|
||||
is.Equal(metadataResult.Name, name)
|
||||
@ -58,12 +58,12 @@ func TestGetEventResponseSummary(t *testing.T) {
|
||||
Latest: date.Today().Add(1),
|
||||
})
|
||||
is.NoErr(err)
|
||||
return event.AlphaID
|
||||
return event.EventID
|
||||
}
|
||||
|
||||
getTotalResponses := func(is *is.I, eventID string) int {
|
||||
responses, err := store.GetEventResponseSummary(context.Background(), back.GetEventResponseSummaryQuery{
|
||||
AlphaID: eventID,
|
||||
EventID: eventID,
|
||||
})
|
||||
is.NoErr(err)
|
||||
return responses.TotalResponses
|
||||
@ -71,7 +71,7 @@ func TestGetEventResponseSummary(t *testing.T) {
|
||||
|
||||
createEmptyResponse := func(is *is.I, eventID string) {
|
||||
_, err = store.CreateEventResponse(context.Background(), back.CreateEventResponseCommand{
|
||||
EventAlphaID: eventID,
|
||||
EventID: eventID,
|
||||
})
|
||||
is.NoErr(err)
|
||||
}
|
||||
@ -117,7 +117,7 @@ func TestCreateEventResponse(t *testing.T) {
|
||||
Latest: date.Today().Add(1),
|
||||
})
|
||||
is.NoErr(err)
|
||||
return event.AlphaID
|
||||
return event.EventID
|
||||
}
|
||||
|
||||
t.Run("saves GuestName", func(t *testing.T) {
|
||||
@ -126,14 +126,14 @@ func TestCreateEventResponse(t *testing.T) {
|
||||
|
||||
const guestName = "Etaoin Shrdlu"
|
||||
createResult, err := store.CreateEventResponse(context.Background(), back.CreateEventResponseCommand{
|
||||
EventAlphaID: eventID,
|
||||
EventID: eventID,
|
||||
GuestName: guestName,
|
||||
})
|
||||
is.NoErr(err)
|
||||
|
||||
getResult, err := store.GetEventResponse(context.Background(), back.GetEventResponseQuery{
|
||||
EventAlphaID: eventID,
|
||||
ResponseAlphaID: createResult.ResponseAlphaID,
|
||||
EventID: eventID,
|
||||
ResponseID: createResult.ResponseID,
|
||||
})
|
||||
is.NoErr(err)
|
||||
is.Equal(getResult.GuestName, guestName)
|
||||
@ -157,15 +157,15 @@ func TestCreateEventResponse(t *testing.T) {
|
||||
latest: {2: {}, 4: {}, 6: {}},
|
||||
}
|
||||
createResponseResult, err := store.CreateEventResponse(context.Background(), back.CreateEventResponseCommand{
|
||||
EventAlphaID: event.AlphaID,
|
||||
EventID: event.EventID,
|
||||
GuestName: "Etaoin Shrdlu",
|
||||
DateHours: dateHours,
|
||||
})
|
||||
is.NoErr(err)
|
||||
|
||||
response, err := store.GetEventResponse(context.Background(), back.GetEventResponseQuery{
|
||||
EventAlphaID: event.AlphaID,
|
||||
ResponseAlphaID: createResponseResult.ResponseAlphaID,
|
||||
EventID: event.EventID,
|
||||
ResponseID: createResponseResult.ResponseID,
|
||||
})
|
||||
is.NoErr(err)
|
||||
is.Equal(dateHours, response.DateHours)
|
||||
|
@ -154,7 +154,7 @@ func internalServerError(w http.ResponseWriter, err error) bool {
|
||||
func (h *handler) handleVote(w http.ResponseWriter, r *http.Request) {
|
||||
eventAlphaID := r.URL.Query().Get(fieldNameEventID)
|
||||
event, err := h.store.GetEventMetadata(context.Background(), back.GetEventMetadataQuery{
|
||||
AlphaID: eventAlphaID,
|
||||
EventID: eventAlphaID,
|
||||
})
|
||||
if notFound(w, err, "Event not found") || internalServerError(w, err) {
|
||||
return
|
||||
@ -181,7 +181,7 @@ func (h *handler) handleDoVote(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Printf("received form: %+v\n", r.Form)
|
||||
|
||||
cmd := back.CreateEventResponseCommand{
|
||||
EventAlphaID: r.Form.Get(fieldNameEventID),
|
||||
EventID: r.Form.Get(fieldNameEventID),
|
||||
GuestName: r.Form.Get(fieldNameGuestName),
|
||||
DateHours: make(map[date.Date]map[int]struct{}),
|
||||
}
|
||||
@ -207,22 +207,22 @@ func (h *handler) handleDoVote(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
var successQuery = make(url.Values)
|
||||
successQuery.Add(fieldNameEventID, cmd.EventAlphaID)
|
||||
successQuery.Add(fieldNameResponseID, eventResponse.ResponseAlphaID)
|
||||
successQuery.Add(fieldNameEventID, cmd.EventID)
|
||||
successQuery.Add(fieldNameResponseID, eventResponse.ResponseID)
|
||||
http.Redirect(w, r, pathVoteSuccess+"?"+successQuery.Encode(), http.StatusSeeOther)
|
||||
}
|
||||
|
||||
func (h *handler) handleVoteSuccess(w http.ResponseWriter, r *http.Request) {
|
||||
eventResponse, err := h.store.GetEventResponse(r.Context(), back.GetEventResponseQuery{
|
||||
EventAlphaID: r.URL.Query().Get(fieldNameEventID),
|
||||
ResponseAlphaID: r.URL.Query().Get(fieldNameResponseID),
|
||||
EventID: r.URL.Query().Get(fieldNameEventID),
|
||||
ResponseID: r.URL.Query().Get(fieldNameResponseID),
|
||||
})
|
||||
if notFound(w, err, "Event response not found.") || internalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
event, err := h.store.GetEventMetadata(r.Context(), back.GetEventMetadataQuery{
|
||||
AlphaID: r.URL.Query().Get(fieldNameEventID),
|
||||
EventID: r.URL.Query().Get(fieldNameEventID),
|
||||
})
|
||||
if notFound(w, err, "Event not found") || internalServerError(w, err) {
|
||||
return
|
||||
@ -295,7 +295,7 @@ func (h *handler) handleDoCreate(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
var successQuery = make(url.Values)
|
||||
successQuery.Add(fieldNameEventID, event.AlphaID)
|
||||
successQuery.Add(fieldNameEventID, event.EventID)
|
||||
successQuery.Add(fieldNameAdminCode, event.AdminCode)
|
||||
http.Redirect(w, r, pathCreateSuccess+"?"+successQuery.Encode(), http.StatusSeeOther)
|
||||
}
|
||||
@ -303,7 +303,7 @@ func (h *handler) handleDoCreate(w http.ResponseWriter, r *http.Request) {
|
||||
func (h *handler) handleCreateSuccess(w http.ResponseWriter, r *http.Request) {
|
||||
eventID := r.URL.Query().Get(fieldNameEventID)
|
||||
event, err := h.store.GetEventMetadata(r.Context(), back.GetEventMetadataQuery{
|
||||
AlphaID: eventID,
|
||||
EventID: eventID,
|
||||
})
|
||||
if notFound(w, err, "Event not found") || internalServerError(w, err) {
|
||||
return
|
||||
@ -351,14 +351,14 @@ func (h *handler) handleAdmin(w http.ResponseWriter, r *http.Request) {
|
||||
query := r.URL.Query()
|
||||
eventID := query.Get(fieldNameEventID)
|
||||
metadata, err := h.store.GetEventMetadata(context.Background(), back.GetEventMetadataQuery{
|
||||
AlphaID: eventID,
|
||||
EventID: eventID,
|
||||
})
|
||||
if notFound(w, err, "Event not found") || internalServerError(w, err) {
|
||||
return
|
||||
}
|
||||
|
||||
responses, err := h.store.GetEventResponseSummary(context.Background(), back.GetEventResponseSummaryQuery{
|
||||
AlphaID: eventID,
|
||||
EventID: eventID,
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprint(w, err)
|
||||
|
4
todo.txt
4
todo.txt
@ -13,10 +13,7 @@ Consider redirecting from /create/do to admin
|
||||
|
||||
Cleanup:
|
||||
------------
|
||||
Make the sqlite pool size configurable
|
||||
Get rid of the surrogate keys
|
||||
Give response (and therefore response_time) a composite key
|
||||
Rename all of the alpha ID nonsense
|
||||
Get rid of the Stat call in NewStore.
|
||||
I can't remember the details. I think the old version
|
||||
of sqlitex was panicking if I failed to open a DB file.
|
||||
@ -25,6 +22,7 @@ Get rid of the Stat call in NewStore.
|
||||
Add optional subtitles to (*front.handler).writePage
|
||||
Take a Page struct with title, subtitle, and contents.
|
||||
Make titles optional in (*front.handler).writePage
|
||||
Make the sqlite pool size configurable
|
||||
|
||||
|
||||
More features:
|
||||
|
Loading…
Reference in New Issue
Block a user