From 348a8ab926f052038aedd0c78c02b1c69828f98f Mon Sep 17 00:00:00 2001
From: Dominik Roos <roos@anapaya.net>
Date: Wed, 16 Oct 2024 22:37:22 +0200
Subject: [PATCH] feature: support in-memory database

In some cases, static user data is sufficient and changes to the users
do not need to be persisted. This change introduces a special file path
`:memory:` (similar to sqlite3) that allows the user to specify that the
database is in-memory only. Changes will not be persisted on the file
system.
---
 pkg/identity/database.go      | 18 ++++++++++++------
 pkg/identity/database_test.go | 27 +++++++++++++++++++++++----
 2 files changed, 35 insertions(+), 10 deletions(-)

diff --git a/pkg/identity/database.go b/pkg/identity/database.go
index 79dd8a7..aed736c 100644
--- a/pkg/identity/database.go
+++ b/pkg/identity/database.go
@@ -112,6 +112,7 @@ type Database struct {
 	refID           map[string]*User
 	refAPIKey       map[string]*User
 	path            string
+	inMemory        bool
 }
 
 // NewDatabase return an instance of Database.
@@ -127,14 +128,17 @@ func NewDatabase(fp string) (*Database, error) {
 		refID:           make(map[string]*User),
 		refEmailAddress: make(map[string]*User),
 		refAPIKey:       make(map[string]*User),
+		inMemory:        fp == ":memory:",
 	}
 	fileInfo, err := os.Stat(fp)
 	if err != nil {
-		if !os.IsNotExist(err) {
-			return nil, errors.ErrNewDatabase.WithArgs(fp, err)
-		}
-		if err := os.MkdirAll(filepath.Dir(fp), 0700); err != nil {
-			return nil, errors.ErrNewDatabase.WithArgs(fp, err)
+		if !db.inMemory {
+			if !os.IsNotExist(err) {
+				return nil, errors.ErrNewDatabase.WithArgs(fp, err)
+			}
+			if err := os.MkdirAll(filepath.Dir(fp), 0700); err != nil {
+				return nil, errors.ErrNewDatabase.WithArgs(fp, err)
+			}
 		}
 		db.Version = app.Version
 		db.enforceDefaultPolicy()
@@ -481,11 +485,13 @@ func (db *Database) Copy(fp string) error {
 func (db *Database) commit() error {
 	db.Revision++
 	db.LastModified = time.Now().UTC()
+	if db.inMemory {
+		return nil
+	}
 	data, err := json.MarshalIndent(db, "", "  ")
 	if err != nil {
 		return errors.ErrDatabaseCommit.WithArgs(db.path, err)
 	}
-
 	if err := os.WriteFile(db.path, []byte(data), 0600); err != nil {
 		return errors.ErrDatabaseCommit.WithArgs(db.path, err)
 	}
diff --git a/pkg/identity/database_test.go b/pkg/identity/database_test.go
index 3a267ba..a8b245f 100644
--- a/pkg/identity/database_test.go
+++ b/pkg/identity/database_test.go
@@ -16,14 +16,15 @@ package identity
 
 import (
 	"fmt"
-	"github.com/google/go-cmp/cmp"
-	"github.com/greenpau/go-authcrunch/internal/tests"
-	"github.com/greenpau/go-authcrunch/pkg/errors"
-	"github.com/greenpau/go-authcrunch/pkg/requests"
 	"path"
 	"path/filepath"
 	"testing"
 	"time"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/greenpau/go-authcrunch/internal/tests"
+	"github.com/greenpau/go-authcrunch/pkg/errors"
+	"github.com/greenpau/go-authcrunch/pkg/requests"
 )
 
 var (
@@ -117,6 +118,24 @@ func TestNewDatabase(t *testing.T) {
 				"user_count": 0,
 			},
 		},
+		{
+			name: "test create new in-memory database",
+			path: ":memory:",
+			req: &requests.Request{
+				User: requests.User{
+					Username: "jsmith",
+					Password: passwd,
+					Email:    "jsmith@gmail.com",
+					FullName: "Smith, John",
+					Roles:    []string{"viewer", "editor", "admin"},
+				},
+			},
+			backup: filepath.Join(tmpDir, "user_db_backup.json"),
+			want: map[string]interface{}{
+				"path":       ":memory:",
+				"user_count": 0,
+			},
+		},
 		{
 			name: "test new database is directory",
 			path: tmpDir,