adding basic data storage

This commit is contained in:
2024-12-27 22:34:43 +01:00
parent ab1b709c77
commit aeb9de71a1
9 changed files with 259 additions and 15 deletions

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"net/http"
"crowsnest-frontend/internal/model"
"crowsnest-frontend/internal/data"
)
func test(w http.ResponseWriter, req *http.Request) {
@@ -14,6 +15,11 @@ func test(w http.ResponseWriter, req *http.Request) {
}
}
func PrintAllKeys(ds data.IDatastore) {
m, err := ds.GetAllKeys()
fmt.Println(m, err)
}
func main() {
http.HandleFunc("/test", test)
@@ -24,7 +30,10 @@ func main() {
a := &model.Article{}
fmt.Println(a.Hash())
fds, _ := data.NewFileDatastore("data.json")
fmt.Println(fds.jetAll())
repo := data.NewDefaultRepository[*model.Article](fds, "article")
fmt.Println(repo.Create(a))
//http.ListenAndServe(":8090", nil)
}

1
data.json Normal file
View File

@@ -0,0 +1 @@
{"article:0":"{\"Identifier\":0,\"SourceUrl\":\"\",\"PublishDate\":\"0001-01-01T00:00:00Z\",\"FetchDate\":\"0001-01-01T00:00:00Z\",\"Title\":\"\",\"Content\":\"\"}"}

View File

@@ -0,0 +1,126 @@
package data
import (
"os"
"maps"
"errors"
"encoding/json"
)
type FileDatastore struct {
/* A very simple datastructure, implementing the IDatastore interface. It
* uses a simple text file to the data as json. */
path string
}
func NewFileDatastore(path string) (*FileDatastore, error) {
/* Creates a new FileDatastore object, creating the storage file in the
* process. */
fds := &FileDatastore{ path: path }
if _, err := fds.readMapObj(); err != nil {
if err := fds.writeMapObj(make(map[string]string)); err != nil { return nil, err }
}
return fds, nil
}
func (fds *FileDatastore) readMapObj() (map[string]string, error) {
/* Read the contents of the storage file and convert to a map object. May
* throw an error, if the file does not exit or the file content can not be
* converted. */
dat, err := os.ReadFile(fds.path)
if err != nil { return nil, err }
var mapobj map[string]string
err = json.Unmarshal(dat, &mapobj)
if err != nil { return nil, err }
return mapobj, nil
}
func (fds *FileDatastore) writeMapObj(m map[string]string) error {
/* Write the map object to the storage file. Will overwrite the content of
* the file. May throw an error, if the file cannot be created or written
* to. */
file, err := os.Create(fds.path)
if err != nil { return err }
defer file.Close()
encoder := json.NewEncoder(file)
if err := encoder.Encode(m); err != nil { return err }
return nil
}
// --- implement IDatastore interface ---
func (fds *FileDatastore) Set(key string, val string) error {
/* Sets the key value pair given, overwriting if the key already exists.
* May through an error if the file cannot be opened or the contents cannot
* be decoded correctly. */
m, err := fds.readMapObj()
if err != nil { return err }
m[key] = val
err = fds.writeMapObj(m)
if err != nil { return err }
return nil
}
func (fds *FileDatastore) KeyExists(key string) (bool, error) {
/* Check if for the given key a entry does exit. May through an error if
* the file cannot be opened or the contents cannot be decoded correctly. */
m, err := fds.readMapObj()
if err != nil { return false, err }
_, ok := m[key]
return ok, nil
}
func (fds *FileDatastore) Get(key string) (string, error) {
/* Gets the value for the given key. May through an error if the key does
* not exit, the file cannot be opened or the contents cannot be decoded
* correctly. */
m, err := fds.readMapObj()
if err != nil { return "", err }
val, ok := m[key]
if !ok { return "", errors.New("key not found") }
return val, nil
}
func (fds *FileDatastore) GetAll() (map[string]string, error) {
/* Gets all the key value pairs from the file and returns them as a map
* object. May through an error if the file cannot be opened or the contents
* cannot be decoded correctly. */
return fds.readMapObj()
}
func (fds *FileDatastore) GetAllKeys() (map[string]bool, error) {
/* Gets all the key the file and returns them as a map object. May through
* an error if the file cannot be opened or the contents cannot be decoded
* correctly. */
m, err := fds.readMapObj()
if err != nil { return nil, err }
out := make(map[string]bool)
for key := range maps.Keys(m) {
out[key] = true
}
return out, nil
}

View File

@@ -0,0 +1,9 @@
package data
type IDatastore interface {
Set(key string, val string) error
KeyExists(key string) (bool, error)
Get(key string) (string, error)
GetAll() (map[string]string, error)
GetAllKeys() (map[string]bool, error)
}

View File

@@ -0,0 +1,5 @@
package data
type IIdentifiable interface {
Id() uint
}

View File

@@ -0,0 +1,89 @@
package data
import (
"encoding/json"
"errors"
"strconv"
)
type IRepository[T IIdentifiable] interface {
/* An interface to manage generic structure objects persistently. Should
* use an IDatastore as the interface that actually stores and retrieves the
* data from an external source. */
Create(t T) error
Update(t T) error
Delete(t T) error
GetAll() ([]T, error)
GetById(id uint) (T, error)
GetByCriteria(c ISearchCriteria[T]) ([]T, error)
}
// --- default implementation ---
type DefaultRepository[T IIdentifiable] struct {
ds IDatastore
prefix string
}
func NewDefaultRepository[T IIdentifiable](ds IDatastore, prefix string) *DefaultRepository[T] {
return &DefaultRepository[T]{ ds: ds, prefix: prefix }
}
func (repo *DefaultRepository[T]) Create(t T) error {
/* Creates a new entry in the repository with the given object t. Throws an
* error if there already exists an entry with the same id, the json
* encoding fails or the connection to the IDatastore fails. */
key := repo.prefix + ":" + strconv.FormatUint(uint64(t.Id()), 10)
exists, err := repo.ds.KeyExists(key)
if err != nil { return err }
if exists { return errors.New("key already exits") }
d, err := json.Marshal(t)
if err != nil { return err }
err = repo.ds.Set(key, string(d))
if err != nil { return err }
return nil
}
func (repo *DefaultRepository[T]) Update(t T) error {
/* Updates the entry with the same id as t in the repository with the
* values of t. Trows an error if the json encoding fails or the connection
* to the IDatastore fails. */
key := repo.prefix + ":" + strconv.FormatUint(uint64(t.Id()), 10)
exists, err := repo.ds.KeyExists(key)
if err != nil { return err }
if !exists { return errors.New("key does not exits") }
d, err := json.Marshal(t)
if err != nil { return err }
err = repo.ds.Set(key, string(d))
if err != nil { return err }
return nil
}
func (repo *DefaultRepository[T]) Delete(t T) error {
// TODO
return nil
}
func (repo *DefaultRepository[T]) GetAll() ([]T, error) {
// TODO
return nil, nil
}
func (repo *DefaultRepository[T]) GetById(id uint) (T, error) {
// TODO
return *new(T), nil
}
func (repo *DefaultRepository[T]) GetByCriteria(c ISearchCriteria[T]) ([]T, error) {
// TODO
return nil, nil
}

View File

@@ -0,0 +1,5 @@
package data
type ISearchCriteria[T any] interface {
Matches(t T) bool
}

View File

@@ -1,8 +0,0 @@
package data
type Datastore interface {
Set(key string) error
Get(key string) string, error
GetAll(rowkey string) map[string]string, error
GetAllKeys() map[string]bool, error
}

View File

@@ -6,8 +6,8 @@ import (
"encoding/hex"
)
type Article struct {
Identifier uint
SourceUrl string
PublishDate time.Time
FetchDate time.Time
@@ -15,6 +15,7 @@ type Article struct {
Content string
}
func (article *Article) Hash() string {
/* Generates a hash based on the source url of the article. Can be used to
* identify the article. */
@@ -22,3 +23,10 @@ func (article *Article) Hash() string {
hash := sha256.Sum256([]byte(article.SourceUrl))
return hex.EncodeToString(hash[:])
}
// --- implementation of the IIdentifiable interface ---
func (article *Article) Id() uint {
return article.Identifier
}