summaryrefslogtreecommitdiff
path: root/storage
diff options
context:
space:
mode:
authorEwy~ <ewy0@protonmail.com>2026-03-31 17:18:35 +0200
committerEwy~ <ewy0@protonmail.com>2026-03-31 17:18:35 +0200
commitcf085e93b7abc53b8ecc78c07210384e0ff8d516 (patch)
tree270bc83c7e4d1c468bd7fcc61d2d65d6f447eda6 /storage
initial commit
Diffstat (limited to 'storage')
-rw-r--r--storage/error.go5
-rw-r--r--storage/link.go63
-rw-r--r--storage/saveable.go7
-rw-r--r--storage/sqlite/sqlite.go132
-rw-r--r--storage/storage.go15
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]