package back import ( "context" "errors" "log" "os" "time" "crawshaw.io/sqlite" "crawshaw.io/sqlite/sqlitex" ) type GenString func(length int) (string, error) type Store struct { pool *sqlitex.Pool genString GenString } func NewStore(filename string, genString GenString) (*Store, error) { var needCreate bool if _, err := os.Stat(filename); os.IsNotExist(err) { needCreate = true } // If the file exists, then assume it was created properly. pool, err := sqlitex.Open(filename, 0, 10) if err != nil { return nil, err } store := &Store{ pool: pool, genString: genString, } if needCreate { log.Println("creating schema") err = store.createSchema() if err != nil { defer pool.Close() return nil, err } } return store, nil } func (s *Store) Close() error { return s.pool.Close() } const schema = ` CREATE TABLE event ( id INTEGER PRIMARY KEY, alpha_id TEXT NOT NULL, 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) ); CREATE TABLE response ( id INTEGER PRIMARY KEY, alpha_id TEXT NOT NULL, event_id INTEGER NOT NULL, UNIQUE (alpha_id), FOREIGN KEY (event_id) REFERENCES event(id) ); CREATE TABLE response_time ( response_id INTEGER PRIMARY KEY, time INTEGER NOT NULL, CHECK (0 <= time < 24) );` func (s *Store) createSchema() error { conn := s.pool.Get(context.Background()) defer s.pool.Put(conn) return sqlitex.ExecScript(conn, schema) } type CreateEventCommand struct { Name string Description string EarliestDate, LatestDate time.Time } type CreateEventResult struct { AlphaID, AdminCode string } const dbDateLayout = "2006-01-02" func (s *Store) CreateEvent(ctx context.Context, cmd CreateEventCommand) (result CreateEventResult, err error) { const alphaIDLength = 10 const adminCodeLength = 10 conn := s.pool.Get(ctx) defer s.pool.Put(conn) alphaID, err := s.genString(alphaIDLength) if err != nil { return } adminCode, err := s.genString(adminCodeLength) if err != nil { return } const query = ` INSERT INTO event(alpha_id, admin_code, name, description, earliest_date, latest_date) VALUES (?, ?, ?, ?, ?, ?);` err = sqlitex.Exec(conn, query, nil, alphaID, adminCode, cmd.Name, cmd.Description, cmd.EarliestDate.Format(dbDateLayout), cmd.LatestDate.Format(dbDateLayout), ) if err != nil { return } result.AdminCode = adminCode result.AlphaID = alphaID return } type GetEventQuery struct { AlphaID string } type GetEventResult struct { Name string Description string EarliestDate, LatestDate time.Time } func (s *Store) GetEvent(ctx context.Context, query GetEventQuery) (GetEventResult, error) { conn := s.pool.Get(ctx) defer s.pool.Put(conn) const dbQuery = ` SELECT name, description, earliest_date, latest_date FROM event WHERE alpha_id = ?;` var result GetEventResult var found bool err := sqlitex.Exec(conn, dbQuery, func(stmt *sqlite.Stmt) error { found = true result.Name = stmt.ColumnText(0) result.Description = stmt.ColumnText(1) earliestDateString := stmt.ColumnText(2) latestDateString := stmt.ColumnText(3) var err error result.EarliestDate, err = time.Parse(dbDateLayout, earliestDateString) if err != nil { return err } result.LatestDate, err = time.Parse(dbDateLayout, latestDateString) return err }, query.AlphaID) if err != nil { return GetEventResult{}, err } if !found { // TODO return a constant or a specific error type for Not Found return GetEventResult{}, errors.New("not found") } return result, nil }