diff options
Diffstat (limited to 'storage')
| -rw-r--r-- | storage/error.go | 5 | ||||
| -rw-r--r-- | storage/link.go | 63 | ||||
| -rw-r--r-- | storage/saveable.go | 7 | ||||
| -rw-r--r-- | storage/sqlite/sqlite.go | 132 | ||||
| -rw-r--r-- | storage/storage.go | 15 |
5 files changed, 222 insertions, 0 deletions
diff --git a/storage/error.go b/storage/error.go new file mode 100644 index 0000000..956a564 --- /dev/null +++ b/storage/error.go @@ -0,0 +1,5 @@ +package storage + +import "errors" + +var NotFoundError = errors.New("link not found")
\ No newline at end of file diff --git a/storage/link.go b/storage/link.go new file mode 100644 index 0000000..f334f42 --- /dev/null +++ b/storage/link.go @@ -0,0 +1,63 @@ +package storage + +import ( + "encoding/json" + "errors" + "github.com/google/uuid" + "time" +) + +type Link struct { + Id uuid.UUID + Target string + OpensFrom time.Time + OpensLeft int +} + +var Null = uuid.UUID{} + +func (l Link) Save(storage Storage[Link, uuid.UUID]) (uuid.UUID, error) { + return storage.Save(l) +} + +func (l Link) Serialize() ([]byte, error) { + return json.Marshal(l) +} + +func (l Link) Use() error { + if e := l.Valid(); e != nil { + return e + } + + remaining := l.OpensLeft - 1 + if remaining < 1 { + _ = Current.Delete(l) + return nil + } + n := Link{ + Id: l.Id, + Target: l.Target, + OpensFrom: l.OpensFrom, + OpensLeft: remaining, + } + return n.Update(Current) +} + +func (l Link) Update(storage Storage[Link, uuid.UUID]) error { + return storage.Update(l) +} + +var NoOpensError = errors.New("no opens left") +var NotYetOpenError = errors.New("not yet open") + +func (l Link) Valid() error { + if l.OpensLeft < 1 { + return NoOpensError + } + + if time.Now().Before(l.OpensFrom) { + return NotYetOpenError + } + + return nil +} diff --git a/storage/saveable.go b/storage/saveable.go new file mode 100644 index 0000000..46a53f2 --- /dev/null +++ b/storage/saveable.go @@ -0,0 +1,7 @@ +package storage + +type Saveable[K comparable] interface { + Save(storage Storage[Saveable[K], K]) (K, error) + Update(storage Storage[Saveable[K], K]) (K, error) + Serialize() ([]byte, error) +} diff --git a/storage/sqlite/sqlite.go b/storage/sqlite/sqlite.go new file mode 100644 index 0000000..9372828 --- /dev/null +++ b/storage/sqlite/sqlite.go @@ -0,0 +1,132 @@ +package sqlite + +import ( + "database/sql" + "delayed.link/storage" + "encoding/json" + "errors" + "github.com/google/uuid" + _ "github.com/mattn/go-sqlite3" + "github.com/spf13/pflag" + "log" +) + +var ( + dbFile = pflag.StringP("dbfile", "d", "delayedlink.db", "") +) + +type SqliteStorage struct { + *sql.DB +} + +func (s *SqliteStorage) Close() error { + return s.DB.Close() +} + +func (s *SqliteStorage) Save(item storage.Link) (uuid.UUID, error) { + stmt, err := s.Prepare("INSERT INTO links VALUES (?, ?)") + if err != nil { + return storage.Null, err + } + id := uuid.New() + item.Id = id + obj, err := item.Serialize() + if err != nil { + return storage.Null, err + } + _, err = stmt.Exec(id, obj) + if err != nil { + return storage.Null, err + } + return id, nil +} + +const insertStatement = `SELECT (obj) FROM links WHERE id = ? LIMIT 1` + +func (s *SqliteStorage) Load(key uuid.UUID) (*storage.Link, error) { + stmt, err := s.DB.Prepare(insertStatement) + if err != nil { + return nil, err + } + rows, err := stmt.Query(key.String()) + if err != nil { + return nil, err + } + defer rows.Close() + var link *storage.Link + var data []byte + if !rows.Next() { + return nil, storage.NotFoundError + } + err = rows.Scan(&data) + if err != nil { + return nil, err + } + err = json.Unmarshal(data, &link) + if err != nil { + return nil, err + } + + return link, nil +} + +const creation_sql = `CREATE TABLE IF NOT EXISTS links ( + id UUID PRIMARY KEY, + obj JSONB NOT NULL +);` + +func New() *SqliteStorage { + db, err := sql.Open("sqlite3", *dbFile) + if err != nil { + log.Panic(err) + } + + _, err = db.Exec(creation_sql) + if err != nil { + panic(err) + } + + return &SqliteStorage{ + db, + } +} + +const deleteQuery = `DELETE FROM links WHERE id = ?` + +var NotFoundError = errors.New("entry not found") + +func (s *SqliteStorage) Delete(item storage.Link) error { + stmt, err := s.DB.Prepare(deleteQuery) + if err != nil { + return err + } + res, err := stmt.Exec(item.Id) + if err != nil { + return err + } + if aff, err := res.RowsAffected(); aff < 1 || err != nil { + if err == nil { + return NotFoundError + } + return err + } + return nil +} + +const updateQuery = `UPDATE links SET obj = ? WHERE id = ?` + +func (s *SqliteStorage) Update(item storage.Link) error { + stmt, err := s.DB.Prepare(updateQuery) + if err != nil { + return err + } + obj, err := item.Serialize() + if err != nil { + return err + } + _, err = stmt.Exec(item.Id, obj) + if err != nil { + return err + } + return nil +} diff --git a/storage/storage.go b/storage/storage.go new file mode 100644 index 0000000..94d7056 --- /dev/null +++ b/storage/storage.go @@ -0,0 +1,15 @@ +package storage + +import ( + "github.com/google/uuid" +) + +type Storage[T any, K comparable] interface { + Save(item T) (K, error) + Update(item T) error + Load(key K) (*T, error) + Delete(item T) error + Close() error +} + +var Current Storage[Link, uuid.UUID] |
