Merge pull request 'Sqlc' (#5) from Sqlc into main

Reviewed-on: https://git.infra.nkode.tech/dkelly/go-nkode/pulls/5
This commit is contained in:
dkelly
2024-12-04 22:45:03 +00:00
31 changed files with 1270 additions and 539 deletions

1
.env.test.example Normal file
View File

@@ -0,0 +1 @@
TEST_DB=/path/to/test.db

View File

@@ -43,15 +43,18 @@ func main() {
if dbPath == "" { if dbPath == "" {
log.Fatalf("SQLITE_DB=/path/to/nkode.db not set") log.Fatalf("SQLITE_DB=/path/to/nkode.db not set")
} }
db := db.NewSqliteDB(dbPath) sqlitedb, err := db.NewSqliteDB(dbPath)
defer db.CloseDb() if err != nil {
log.Fatalf("%v", err)
}
defer sqlitedb.Close()
sesClient := email.NewSESClient() sesClient := email.NewSESClient()
emailQueue := email.NewEmailQueue(emailQueueBufferSize, maxEmailsPerSecond, &sesClient) emailQueue := email.NewEmailQueue(emailQueueBufferSize, maxEmailsPerSecond, &sesClient)
emailQueue.Start() emailQueue.Start()
defer emailQueue.Stop() defer emailQueue.Stop()
nkodeApi := api.NewNKodeAPI(db, emailQueue) nkodeApi := api.NewNKodeAPI(sqlitedb, emailQueue)
AddDefaultCustomer(nkodeApi) AddDefaultCustomer(nkodeApi)
handler := api.NKodeHandler{Api: nkodeApi} handler := api.NKodeHandler{Api: nkodeApi}

View File

@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go-nkode/internal/api" "go-nkode/internal/api"
"go-nkode/internal/entities"
"go-nkode/internal/models" "go-nkode/internal/models"
"go-nkode/internal/security" "go-nkode/internal/security"
"io" "io"
@@ -19,7 +20,7 @@ func TestApi(t *testing.T) {
newCustomerBody := models.NewCustomerPost{ newCustomerBody := models.NewCustomerPost{
NKodePolicy: models.NewDefaultNKodePolicy(), NKodePolicy: models.NewDefaultNKodePolicy(),
} }
kp := models.KeypadDimension{ kp := entities.KeypadDimension{
AttrsPerKey: 14, AttrsPerKey: 14,
NumbOfKeys: 10, NumbOfKeys: 10,
} }
@@ -40,8 +41,8 @@ func TestApi(t *testing.T) {
passcodeLen := 4 passcodeLen := 4
setInterface := signupInterfaceResp.UserIdxInterface setInterface := signupInterfaceResp.UserIdxInterface
userPasscode := setInterface[:passcodeLen] userPasscode := setInterface[:passcodeLen]
kpSet := models.KeypadDimension{NumbOfKeys: kp.NumbOfKeys, AttrsPerKey: kp.NumbOfKeys} kpSet := entities.KeypadDimension{NumbOfKeys: kp.NumbOfKeys, AttrsPerKey: kp.NumbOfKeys}
setKeySelection, err := models.SelectKeyByAttrIdx(setInterface, userPasscode, kpSet) setKeySelection, err := entities.SelectKeyByAttrIdx(setInterface, userPasscode, kpSet)
assert.NoError(t, err) assert.NoError(t, err)
setNKodeBody := models.SetNKodePost{ setNKodeBody := models.SetNKodePost{
CustomerId: customerResp.CustomerId, CustomerId: customerResp.CustomerId,
@@ -51,7 +52,7 @@ func TestApi(t *testing.T) {
var setNKodeResp models.SetNKodeResp var setNKodeResp models.SetNKodeResp
testApiPost(t, base+api.SetNKode, setNKodeBody, &setNKodeResp) testApiPost(t, base+api.SetNKode, setNKodeBody, &setNKodeResp)
confirmInterface := setNKodeResp.UserInterface confirmInterface := setNKodeResp.UserInterface
confirmKeySelection, err := models.SelectKeyByAttrIdx(confirmInterface, userPasscode, kpSet) confirmKeySelection, err := entities.SelectKeyByAttrIdx(confirmInterface, userPasscode, kpSet)
assert.NoError(t, err) assert.NoError(t, err)
confirmNKodeBody := models.ConfirmNKodePost{ confirmNKodeBody := models.ConfirmNKodePost{
CustomerId: customerResp.CustomerId, CustomerId: customerResp.CustomerId,
@@ -69,7 +70,7 @@ func TestApi(t *testing.T) {
testApiPost(t, base+api.GetLoginInterface, loginInterfaceBody, &loginInterfaceResp) testApiPost(t, base+api.GetLoginInterface, loginInterfaceBody, &loginInterfaceResp)
assert.Equal(t, loginInterfaceResp.AttrsPerKey, kp.AttrsPerKey) assert.Equal(t, loginInterfaceResp.AttrsPerKey, kp.AttrsPerKey)
assert.Equal(t, loginInterfaceResp.NumbOfKeys, kp.NumbOfKeys) assert.Equal(t, loginInterfaceResp.NumbOfKeys, kp.NumbOfKeys)
loginKeySelection, err := models.SelectKeyByAttrIdx(loginInterfaceResp.UserIdxInterface, userPasscode, kp) loginKeySelection, err := entities.SelectKeyByAttrIdx(loginInterfaceResp.UserIdxInterface, userPasscode, kp)
assert.NoError(t, err) assert.NoError(t, err)
loginBody := models.LoginPost{ loginBody := models.LoginPost{
CustomerId: customerResp.CustomerId, CustomerId: customerResp.CustomerId,
@@ -86,7 +87,7 @@ func TestApi(t *testing.T) {
renewBody := models.RenewAttributesPost{CustomerId: customerResp.CustomerId} renewBody := models.RenewAttributesPost{CustomerId: customerResp.CustomerId}
testApiPost(t, base+api.RenewAttributes, renewBody, nil) testApiPost(t, base+api.RenewAttributes, renewBody, nil)
loginKeySelection, err = models.SelectKeyByAttrIdx(loginInterfaceResp.UserIdxInterface, userPasscode, kp) loginKeySelection, err = entities.SelectKeyByAttrIdx(loginInterfaceResp.UserIdxInterface, userPasscode, kp)
assert.NoError(t, err) assert.NoError(t, err)
loginBody = models.LoginPost{ loginBody = models.LoginPost{
CustomerId: customerResp.CustomerId, CustomerId: customerResp.CustomerId,
@@ -98,7 +99,7 @@ func TestApi(t *testing.T) {
var randomSvgInterfaceResp models.RandomSvgInterfaceResp var randomSvgInterfaceResp models.RandomSvgInterfaceResp
testApiGet(t, base+api.RandomSvgInterface, &randomSvgInterfaceResp, "") testApiGet(t, base+api.RandomSvgInterface, &randomSvgInterfaceResp, "")
assert.Equal(t, models.KeypadMax.TotalAttrs(), len(randomSvgInterfaceResp.Svgs)) assert.Equal(t, entities.KeypadMax.TotalAttrs(), len(randomSvgInterfaceResp.Svgs))
var refreshTokenResp models.RefreshTokenResp var refreshTokenResp models.RefreshTokenResp

View File

@@ -1,20 +0,0 @@
package api
import (
"go-nkode/internal/models"
)
type DbAccessor interface {
GetCustomer(models.CustomerId) (*models.Customer, error)
GetUser(models.UserEmail, models.CustomerId) (*models.User, error)
WriteNewCustomer(models.Customer) error
WriteNewUser(models.User) error
UpdateUserNKode(models.User) error
UpdateUserInterface(models.UserId, models.UserInterface) error
UpdateUserRefreshToken(models.UserId, string) error
Renew(models.CustomerId) error
RefreshUserPasscode(models.User, []int, models.CustomerAttributes) error
RandomSvgInterface(models.KeypadDimension) ([]string, error)
RandomSvgIdxInterface(models.KeypadDimension) (models.SvgIdInterface, error)
GetSvgStringInterface(models.SvgIdInterface) ([]string, error)
}

View File

@@ -5,6 +5,7 @@ import (
"errors" "errors"
"github.com/google/uuid" "github.com/google/uuid"
"go-nkode/config" "go-nkode/config"
"go-nkode/internal/entities"
"go-nkode/internal/models" "go-nkode/internal/models"
"go-nkode/internal/security" "go-nkode/internal/security"
"log" "log"
@@ -108,7 +109,7 @@ func (h *NKodeHandler) GenerateSignupResetInterfaceHandler(w http.ResponseWriter
return return
} }
kp := models.KeypadDimension{ kp := entities.KeypadDimension{
AttrsPerKey: signupResetPost.AttrsPerKey, AttrsPerKey: signupResetPost.AttrsPerKey,
NumbOfKeys: signupResetPost.NumbOfKeys, NumbOfKeys: signupResetPost.NumbOfKeys,
} }

View File

@@ -5,7 +5,9 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/patrickmn/go-cache" "github.com/patrickmn/go-cache"
"go-nkode/config" "go-nkode/config"
"go-nkode/internal/db"
"go-nkode/internal/email" "go-nkode/internal/email"
"go-nkode/internal/entities"
"go-nkode/internal/models" "go-nkode/internal/models"
"go-nkode/internal/security" "go-nkode/internal/security"
"log" "log"
@@ -19,12 +21,12 @@ const (
) )
type NKodeAPI struct { type NKodeAPI struct {
Db DbAccessor Db db.CustomerUserRepository
SignupSessionCache *cache.Cache SignupSessionCache *cache.Cache
EmailQueue *email.EmailQueue EmailQueue *email.Queue
} }
func NewNKodeAPI(db DbAccessor, queue *email.EmailQueue) NKodeAPI { func NewNKodeAPI(db db.CustomerUserRepository, queue *email.Queue) NKodeAPI {
return NKodeAPI{ return NKodeAPI{
Db: db, Db: db,
EmailQueue: queue, EmailQueue: queue,
@@ -33,14 +35,14 @@ func NewNKodeAPI(db DbAccessor, queue *email.EmailQueue) NKodeAPI {
} }
func (n *NKodeAPI) CreateNewCustomer(nkodePolicy models.NKodePolicy, id *models.CustomerId) (*models.CustomerId, error) { func (n *NKodeAPI) CreateNewCustomer(nkodePolicy models.NKodePolicy, id *models.CustomerId) (*models.CustomerId, error) {
newCustomer, err := models.NewCustomer(nkodePolicy) newCustomer, err := entities.NewCustomer(nkodePolicy)
if id != nil { if id != nil {
newCustomer.Id = *id newCustomer.Id = *id
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = n.Db.WriteNewCustomer(*newCustomer) err = n.Db.CreateCustomer(*newCustomer)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -48,7 +50,7 @@ func (n *NKodeAPI) CreateNewCustomer(nkodePolicy models.NKodePolicy, id *models.
return &newCustomer.Id, nil return &newCustomer.Id, nil
} }
func (n *NKodeAPI) GenerateSignupResetInterface(userEmail models.UserEmail, customerId models.CustomerId, kp models.KeypadDimension, reset bool) (*models.GenerateSignupResetInterfaceResp, error) { func (n *NKodeAPI) GenerateSignupResetInterface(userEmail models.UserEmail, customerId models.CustomerId, kp entities.KeypadDimension, reset bool) (*models.GenerateSignupResetInterfaceResp, error) {
user, err := n.Db.GetUser(userEmail, customerId) user, err := n.Db.GetUser(userEmail, customerId)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -61,7 +63,7 @@ func (n *NKodeAPI) GenerateSignupResetInterface(userEmail models.UserEmail, cust
if err != nil { if err != nil {
return nil, err return nil, err
} }
signupSession, err := models.NewSignupResetSession(userEmail, kp, customerId, svgIdxInterface, reset) signupSession, err := entities.NewSignupResetSession(userEmail, kp, customerId, svgIdxInterface, reset)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -94,7 +96,7 @@ func (n *NKodeAPI) SetNKode(customerId models.CustomerId, sessionId models.Sessi
log.Printf("session id does not exist %s", sessionId) log.Printf("session id does not exist %s", sessionId)
return nil, config.ErrSignupSessionDNE return nil, config.ErrSignupSessionDNE
} }
userSession, ok := session.(models.UserSignSession) userSession, ok := session.(entities.UserSignSession)
if !ok { if !ok {
// handle the case where the type assertion fails // handle the case where the type assertion fails
return nil, config.ErrSignupSessionDNE return nil, config.ErrSignupSessionDNE
@@ -113,7 +115,7 @@ func (n *NKodeAPI) ConfirmNKode(customerId models.CustomerId, sessionId models.S
log.Printf("session id does not exist %s", sessionId) log.Printf("session id does not exist %s", sessionId)
return config.ErrSignupSessionDNE return config.ErrSignupSessionDNE
} }
userSession, ok := session.(models.UserSignSession) userSession, ok := session.(entities.UserSignSession)
if !ok { if !ok {
// handle the case where the type assertion fails // handle the case where the type assertion fails
return config.ErrSignupSessionDNE return config.ErrSignupSessionDNE
@@ -129,7 +131,7 @@ func (n *NKodeAPI) ConfirmNKode(customerId models.CustomerId, sessionId models.S
if err = customer.IsValidNKode(userSession.Kp, passcode); err != nil { if err = customer.IsValidNKode(userSession.Kp, passcode); err != nil {
return err return err
} }
user, err := models.NewUser(*customer, string(userSession.UserEmail), passcode, userSession.LoginUserInterface, userSession.Kp) user, err := entities.NewUser(*customer, string(userSession.UserEmail), passcode, userSession.LoginUserInterface, userSession.Kp)
if err != nil { if err != nil {
return err return err
} }
@@ -186,7 +188,7 @@ func (n *NKodeAPI) Login(customerId models.CustomerId, userEmail models.UserEmai
log.Printf("user %s for customer %s dne", userEmail, customerId) log.Printf("user %s for customer %s dne", userEmail, customerId)
return nil, config.ErrUserForCustomerDNE return nil, config.ErrUserForCustomerDNE
} }
passcode, err := models.ValidKeyEntry(*user, *customer, keySelection) passcode, err := entities.ValidKeyEntry(*user, *customer, keySelection)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -213,7 +215,7 @@ func (n *NKodeAPI) RenewAttributes(customerId models.CustomerId) error {
} }
func (n *NKodeAPI) RandomSvgInterface() ([]string, error) { func (n *NKodeAPI) RandomSvgInterface() ([]string, error) {
return n.Db.RandomSvgInterface(models.KeypadMax) return n.Db.RandomSvgInterface(entities.KeypadMax)
} }
func (n *NKodeAPI) RefreshToken(userEmail models.UserEmail, customerId models.CustomerId, refreshToken string) (string, error) { func (n *NKodeAPI) RefreshToken(userEmail models.UserEmail, customerId models.CustomerId, refreshToken string) (string, error) {

View File

@@ -4,6 +4,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go-nkode/internal/db" "go-nkode/internal/db"
"go-nkode/internal/email" "go-nkode/internal/email"
"go-nkode/internal/entities"
"go-nkode/internal/models" "go-nkode/internal/models"
"go-nkode/internal/security" "go-nkode/internal/security"
"os" "os"
@@ -16,8 +17,9 @@ func TestNKodeAPI(t *testing.T) {
dbFile := os.Getenv("TEST_DB") dbFile := os.Getenv("TEST_DB")
db2 := db.NewSqliteDB(dbFile) db2, err := db.NewSqliteDB(dbFile)
defer db2.CloseDb() assert.NoError(t, err)
defer db2.Close()
testNKodeAPI(t, db2) testNKodeAPI(t, db2)
//if _, err := os.Stat(dbFile); err == nil { //if _, err := os.Stat(dbFile); err == nil {
@@ -28,7 +30,7 @@ func TestNKodeAPI(t *testing.T) {
//} //}
} }
func testNKodeAPI(t *testing.T, db DbAccessor) { func testNKodeAPI(t *testing.T, db db.CustomerUserRepository) {
bufferSize := 100 bufferSize := 100
emailsPerSec := 14 emailsPerSec := 14
testClient := email.TestEmailClient{} testClient := email.TestEmailClient{}
@@ -41,7 +43,7 @@ func testNKodeAPI(t *testing.T, db DbAccessor) {
userEmail := models.UserEmail("test_username" + security.GenerateRandomString(12) + "@example.com") userEmail := models.UserEmail("test_username" + security.GenerateRandomString(12) + "@example.com")
passcodeLen := 4 passcodeLen := 4
nkodePolicy := models.NewDefaultNKodePolicy() nkodePolicy := models.NewDefaultNKodePolicy()
keypadSize := models.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys} keypadSize := entities.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys}
nkodeApi := NewNKodeAPI(db, queue) nkodeApi := NewNKodeAPI(db, queue)
customerId, err := nkodeApi.CreateNewCustomer(nkodePolicy, nil) customerId, err := nkodeApi.CreateNewCustomer(nkodePolicy, nil)
assert.NoError(t, err) assert.NoError(t, err)
@@ -51,20 +53,20 @@ func testNKodeAPI(t *testing.T, db DbAccessor) {
sessionIdStr := signupResponse.SessionId sessionIdStr := signupResponse.SessionId
sessionId, err := models.SessionIdFromString(sessionIdStr) sessionId, err := models.SessionIdFromString(sessionIdStr)
assert.NoError(t, err) assert.NoError(t, err)
keypadSize = models.KeypadDimension{AttrsPerKey: numbOfKeys, NumbOfKeys: numbOfKeys} keypadSize = entities.KeypadDimension{AttrsPerKey: numbOfKeys, NumbOfKeys: numbOfKeys}
userPasscode := setInterface[:passcodeLen] userPasscode := setInterface[:passcodeLen]
setKeySelect, err := models.SelectKeyByAttrIdx(setInterface, userPasscode, keypadSize) setKeySelect, err := entities.SelectKeyByAttrIdx(setInterface, userPasscode, keypadSize)
assert.NoError(t, err) assert.NoError(t, err)
confirmInterface, err := nkodeApi.SetNKode(*customerId, sessionId, setKeySelect) confirmInterface, err := nkodeApi.SetNKode(*customerId, sessionId, setKeySelect)
assert.NoError(t, err) assert.NoError(t, err)
confirmKeySelect, err := models.SelectKeyByAttrIdx(confirmInterface, userPasscode, keypadSize) confirmKeySelect, err := entities.SelectKeyByAttrIdx(confirmInterface, userPasscode, keypadSize)
err = nkodeApi.ConfirmNKode(*customerId, sessionId, confirmKeySelect) err = nkodeApi.ConfirmNKode(*customerId, sessionId, confirmKeySelect)
assert.NoError(t, err) assert.NoError(t, err)
keypadSize = models.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys} keypadSize = entities.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys}
loginInterface, err := nkodeApi.GetLoginInterface(userEmail, *customerId) loginInterface, err := nkodeApi.GetLoginInterface(userEmail, *customerId)
assert.NoError(t, err) assert.NoError(t, err)
loginKeySelection, err := models.SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, keypadSize) loginKeySelection, err := entities.SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, keypadSize)
assert.NoError(t, err) assert.NoError(t, err)
_, err = nkodeApi.Login(*customerId, userEmail, loginKeySelection) _, err = nkodeApi.Login(*customerId, userEmail, loginKeySelection)
assert.NoError(t, err) assert.NoError(t, err)
@@ -74,34 +76,34 @@ func testNKodeAPI(t *testing.T, db DbAccessor) {
loginInterface, err = nkodeApi.GetLoginInterface(userEmail, *customerId) loginInterface, err = nkodeApi.GetLoginInterface(userEmail, *customerId)
assert.NoError(t, err) assert.NoError(t, err)
loginKeySelection, err = models.SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, keypadSize) loginKeySelection, err = entities.SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, keypadSize)
assert.NoError(t, err) assert.NoError(t, err)
_, err = nkodeApi.Login(*customerId, userEmail, loginKeySelection) _, err = nkodeApi.Login(*customerId, userEmail, loginKeySelection)
assert.NoError(t, err) assert.NoError(t, err)
/// Reset nKode /// Reset nKode
attrsPerKey = 6 attrsPerKey = 6
keypadSize = models.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys} keypadSize = entities.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys}
resetResponse, err := nkodeApi.GenerateSignupResetInterface(userEmail, *customerId, keypadSize, true) resetResponse, err := nkodeApi.GenerateSignupResetInterface(userEmail, *customerId, keypadSize, true)
assert.NoError(t, err) assert.NoError(t, err)
setInterface = resetResponse.UserIdxInterface setInterface = resetResponse.UserIdxInterface
sessionIdStr = resetResponse.SessionId sessionIdStr = resetResponse.SessionId
sessionId, err = models.SessionIdFromString(sessionIdStr) sessionId, err = models.SessionIdFromString(sessionIdStr)
assert.NoError(t, err) assert.NoError(t, err)
keypadSize = models.KeypadDimension{AttrsPerKey: numbOfKeys, NumbOfKeys: numbOfKeys} keypadSize = entities.KeypadDimension{AttrsPerKey: numbOfKeys, NumbOfKeys: numbOfKeys}
userPasscode = setInterface[:passcodeLen] userPasscode = setInterface[:passcodeLen]
setKeySelect, err = models.SelectKeyByAttrIdx(setInterface, userPasscode, keypadSize) setKeySelect, err = entities.SelectKeyByAttrIdx(setInterface, userPasscode, keypadSize)
assert.NoError(t, err) assert.NoError(t, err)
confirmInterface, err = nkodeApi.SetNKode(*customerId, sessionId, setKeySelect) confirmInterface, err = nkodeApi.SetNKode(*customerId, sessionId, setKeySelect)
assert.NoError(t, err) assert.NoError(t, err)
confirmKeySelect, err = models.SelectKeyByAttrIdx(confirmInterface, userPasscode, keypadSize) confirmKeySelect, err = entities.SelectKeyByAttrIdx(confirmInterface, userPasscode, keypadSize)
err = nkodeApi.ConfirmNKode(*customerId, sessionId, confirmKeySelect) err = nkodeApi.ConfirmNKode(*customerId, sessionId, confirmKeySelect)
assert.NoError(t, err) assert.NoError(t, err)
keypadSize = models.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys} keypadSize = entities.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys}
loginInterface2, err := nkodeApi.GetLoginInterface(userEmail, *customerId) loginInterface2, err := nkodeApi.GetLoginInterface(userEmail, *customerId)
assert.NoError(t, err) assert.NoError(t, err)
loginKeySelection, err = models.SelectKeyByAttrIdx(loginInterface2.UserIdxInterface, userPasscode, keypadSize) loginKeySelection, err = entities.SelectKeyByAttrIdx(loginInterface2.UserIdxInterface, userPasscode, keypadSize)
assert.NoError(t, err) assert.NoError(t, err)
_, err = nkodeApi.Login(*customerId, userEmail, loginKeySelection) _, err = nkodeApi.Login(*customerId, userEmail, loginKeySelection)
assert.NoError(t, err) assert.NoError(t, err)

View File

@@ -0,0 +1,21 @@
package db
import (
"go-nkode/internal/entities"
"go-nkode/internal/models"
)
type CustomerUserRepository interface {
GetCustomer(models.CustomerId) (*entities.Customer, error)
GetUser(models.UserEmail, models.CustomerId) (*entities.User, error)
CreateCustomer(entities.Customer) error
WriteNewUser(entities.User) error
UpdateUserNKode(entities.User) error
UpdateUserInterface(models.UserId, entities.UserInterface) error
UpdateUserRefreshToken(models.UserId, string) error
Renew(models.CustomerId) error
RefreshUserPasscode(entities.User, []int, entities.CustomerAttributes) error
RandomSvgInterface(entities.KeypadDimension) ([]string, error)
RandomSvgIdxInterface(entities.KeypadDimension) (models.SvgIdInterface, error)
GetSvgStringInterface(models.SvgIdInterface) ([]string, error)
}

View File

@@ -3,24 +3,25 @@ package db
import ( import (
"errors" "errors"
"fmt" "fmt"
"go-nkode/internal/entities"
"go-nkode/internal/models" "go-nkode/internal/models"
) )
type InMemoryDb struct { type InMemoryDb struct {
Customers map[models.CustomerId]models.Customer Customers map[models.CustomerId]entities.Customer
Users map[models.UserId]models.User Users map[models.UserId]entities.User
userIdMap map[string]models.UserId userIdMap map[string]models.UserId
} }
func NewInMemoryDb() InMemoryDb { func NewInMemoryDb() InMemoryDb {
return InMemoryDb{ return InMemoryDb{
Customers: make(map[models.CustomerId]models.Customer), Customers: make(map[models.CustomerId]entities.Customer),
Users: make(map[models.UserId]models.User), Users: make(map[models.UserId]entities.User),
userIdMap: make(map[string]models.UserId), userIdMap: make(map[string]models.UserId),
} }
} }
func (db *InMemoryDb) GetCustomer(id models.CustomerId) (*models.Customer, error) { func (db *InMemoryDb) GetCustomer(id models.CustomerId) (*entities.Customer, error) {
customer, exists := db.Customers[id] customer, exists := db.Customers[id]
if !exists { if !exists {
return nil, errors.New(fmt.Sprintf("customer %s dne", customer.Id)) return nil, errors.New(fmt.Sprintf("customer %s dne", customer.Id))
@@ -28,7 +29,7 @@ func (db *InMemoryDb) GetCustomer(id models.CustomerId) (*models.Customer, error
return &customer, nil return &customer, nil
} }
func (db *InMemoryDb) GetUser(username models.UserEmail, customerId models.CustomerId) (*models.User, error) { func (db *InMemoryDb) GetUser(username models.UserEmail, customerId models.CustomerId) (*entities.User, error) {
key := userIdKey(customerId, username) key := userIdKey(customerId, username)
userId, exists := db.userIdMap[key] userId, exists := db.userIdMap[key]
if !exists { if !exists {
@@ -41,7 +42,7 @@ func (db *InMemoryDb) GetUser(username models.UserEmail, customerId models.Custo
return &user, nil return &user, nil
} }
func (db *InMemoryDb) WriteNewCustomer(customer models.Customer) error { func (db *InMemoryDb) CreateCustomer(customer entities.Customer) error {
_, exists := db.Customers[customer.Id] _, exists := db.Customers[customer.Id]
if exists { if exists {
@@ -51,7 +52,7 @@ func (db *InMemoryDb) WriteNewCustomer(customer models.Customer) error {
return nil return nil
} }
func (db *InMemoryDb) WriteNewUser(user models.User) error { func (db *InMemoryDb) WriteNewUser(user entities.User) error {
_, exists := db.Customers[user.CustomerId] _, exists := db.Customers[user.CustomerId]
if !exists { if !exists {
return errors.New(fmt.Sprintf("can't add user %s to customer %s: customer dne", user.Email, user.CustomerId)) return errors.New(fmt.Sprintf("can't add user %s to customer %s: customer dne", user.Email, user.CustomerId))
@@ -67,11 +68,11 @@ func (db *InMemoryDb) WriteNewUser(user models.User) error {
return nil return nil
} }
func (db *InMemoryDb) UpdateUserNKode(user models.User) error { func (db *InMemoryDb) UpdateUserNKode(user entities.User) error {
return errors.ErrUnsupported return errors.ErrUnsupported
} }
func (db *InMemoryDb) UpdateUserInterface(userId models.UserId, ui models.UserInterface) error { func (db *InMemoryDb) UpdateUserInterface(userId models.UserId, ui entities.UserInterface) error {
user, exists := db.Users[userId] user, exists := db.Users[userId]
if !exists { if !exists {
return errors.New(fmt.Sprintf("can't update user %s, dne", user.Id)) return errors.New(fmt.Sprintf("can't update user %s, dne", user.Id))
@@ -107,7 +108,7 @@ func (db *InMemoryDb) Renew(id models.CustomerId) error {
return nil return nil
} }
func (db *InMemoryDb) RefreshUserPasscode(user models.User, passocode []int, customerAttr models.CustomerAttributes) error { func (db *InMemoryDb) RefreshUserPasscode(user entities.User, passocode []int, customerAttr entities.CustomerAttributes) error {
err := user.RefreshPasscode(passocode, customerAttr) err := user.RefreshPasscode(passocode, customerAttr)
if err != nil { if err != nil {
return err return err
@@ -116,11 +117,11 @@ func (db *InMemoryDb) RefreshUserPasscode(user models.User, passocode []int, cus
return nil return nil
} }
func (db *InMemoryDb) RandomSvgInterface(kp models.KeypadDimension) ([]string, error) { func (db *InMemoryDb) RandomSvgInterface(kp entities.KeypadDimension) ([]string, error) {
return make([]string, kp.TotalAttrs()), nil return make([]string, kp.TotalAttrs()), nil
} }
func (db *InMemoryDb) RandomSvgIdxInterface(kp models.KeypadDimension) (models.SvgIdInterface, error) { func (db *InMemoryDb) RandomSvgIdxInterface(kp entities.KeypadDimension) (models.SvgIdInterface, error) {
svgs := make(models.SvgIdInterface, kp.TotalAttrs()) svgs := make(models.SvgIdInterface, kp.TotalAttrs())
for idx := range svgs { for idx := range svgs {
svgs[idx] = idx svgs[idx] = idx

View File

@@ -1,444 +1,402 @@
package db package db
import ( import (
"context"
"database/sql" "database/sql"
"errors"
"fmt" "fmt"
"github.com/google/uuid" "github.com/google/uuid"
_ "github.com/mattn/go-sqlite3" // Import the SQLite3 driver _ "github.com/mattn/go-sqlite3" // Import the SQLite3 driver
"go-nkode/config" "go-nkode/config"
"go-nkode/internal/entities"
"go-nkode/internal/models" "go-nkode/internal/models"
"go-nkode/internal/security" "go-nkode/internal/security"
"go-nkode/internal/sqlc"
"go-nkode/internal/utils"
"log" "log"
"sync" "sync"
"time"
) )
type SqliteDB struct { const writeBufferSize = 100
db *sql.DB
stop bool
writeQueue chan WriteTx
wg sync.WaitGroup
}
type sqlcGeneric func(*sqlc.Queries, context.Context, any) error
// WriteTx represents a write transaction
type WriteTx struct { type WriteTx struct {
ErrChan chan error ErrChan chan error
Query string Query sqlcGeneric
Args []any Args interface{}
} }
const ( // SqliteDB represents the SQLite database connection and write queue
writeBuffer = 1000 type SqliteDB struct {
) queries *sqlc.Queries
db *sql.DB
writeQueue chan WriteTx
wg sync.WaitGroup
ctx context.Context
cancel context.CancelFunc
}
// NewSqliteDB initializes a new SqliteDB instance
func NewSqliteDB(path string) (*SqliteDB, error) {
if path == "" {
return nil, errors.New("database path is required")
}
func NewSqliteDB(path string) *SqliteDB {
db, err := sql.Open("sqlite3", path) db, err := sql.Open("sqlite3", path)
if err != nil { if err != nil {
log.Fatal("database didn't open ", err) return nil, fmt.Errorf("failed to open database: %w", err)
} }
sqldb := SqliteDB{
if err := db.Ping(); err != nil {
return nil, fmt.Errorf("failed to connect to database: %w", err)
}
ctx, cancel := context.WithCancel(context.Background())
sqldb := &SqliteDB{
queries: sqlc.New(db),
db: db, db: db,
stop: false, writeQueue: make(chan WriteTx, writeBufferSize),
writeQueue: make(chan WriteTx, writeBuffer), ctx: ctx,
cancel: cancel,
} }
go func() { sqldb.wg.Add(1)
for writeTx := range sqldb.writeQueue { go sqldb.processWriteQueue()
writeTx.ErrChan <- sqldb.writeToDb(writeTx.Query, writeTx.Args)
sqldb.wg.Done()
}
}()
return &sqldb return sqldb, nil
} }
func (d *SqliteDB) CloseDb() { // processWriteQueue handles write transactions from the queue
d.stop = true func (d *SqliteDB) processWriteQueue() {
defer d.wg.Done()
for {
select {
case <-d.ctx.Done():
return
case writeTx := <-d.writeQueue:
err := writeTx.Query(d.queries, d.ctx, writeTx.Args)
writeTx.ErrChan <- err
}
}
}
func (d *SqliteDB) Close() error {
d.cancel()
d.wg.Wait() d.wg.Wait()
if err := d.db.Close(); err != nil { close(d.writeQueue)
// If db.Close() returns an error, panic return d.db.Close()
panic(fmt.Sprintf("Failed to close the database: %v", err))
}
} }
func (d *SqliteDB) WriteNewCustomer(c models.Customer) error { func (d *SqliteDB) CreateCustomer(c entities.Customer) error {
query := ` queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error {
INSERT INTO customer ( params, ok := args.(sqlc.CreateCustomerParams)
id if !ok {
,max_nkode_len return fmt.Errorf("invalid argument type: expected CreateCustomerParams")
,min_nkode_len
,distinct_sets
,distinct_attributes
,lock_out
,expiration
,attribute_values
,set_values
,last_renew
,created_at
)
VALUES (?,?,?,?,?,?,?,?,?,?,?)
`
args := []any{
uuid.UUID(c.Id), c.NKodePolicy.MaxNkodeLen, c.NKodePolicy.MinNkodeLen, c.NKodePolicy.DistinctSets,
c.NKodePolicy.DistinctAttributes, c.NKodePolicy.LockOut, c.NKodePolicy.Expiration,
c.Attributes.AttrBytes(), c.Attributes.SetBytes(), timeStamp(), timeStamp(),
} }
return d.addWriteTx(query, args) return q.CreateCustomer(ctx, params)
} }
func (d *SqliteDB) WriteNewUser(u models.User) error { return d.enqueueWriteTx(queryFunc, c.ToSqlcCreateCustomerParams())
query := ` }
INSERT INTO user (
id func (d *SqliteDB) WriteNewUser(u entities.User) error {
,email queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error {
,renew params, ok := args.(sqlc.CreateUserParams)
,refresh_token if !ok {
,customer_id return fmt.Errorf("invalid argument type: expected CreateUserParams")
,code }
,mask return q.CreateUser(ctx, params)
,attributes_per_key }
,number_of_keys // Use the wrapped function in enqueueWriteTx
,alpha_key
,set_key renew := 0
,pass_key
,mask_key
,salt
,max_nkode_len
,idx_interface
,svg_id_interface
,created_at
)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
`
var renew int
if u.Renew { if u.Renew {
renew = 1 renew = 1
} else { }
renew = 0 // Map entities.User to CreateUserParams
params := sqlc.CreateUserParams{
ID: uuid.UUID(u.Id).String(),
Email: string(u.Email),
Renew: int64(renew),
RefreshToken: sql.NullString{String: u.RefreshToken, Valid: u.RefreshToken != ""},
CustomerID: uuid.UUID(u.CustomerId).String(),
Code: u.EncipheredPasscode.Code,
Mask: u.EncipheredPasscode.Mask,
AttributesPerKey: int64(u.Kp.AttrsPerKey),
NumberOfKeys: int64(u.Kp.NumbOfKeys),
AlphaKey: security.Uint64ArrToByteArr(u.CipherKeys.AlphaKey),
SetKey: security.Uint64ArrToByteArr(u.CipherKeys.SetKey),
PassKey: security.Uint64ArrToByteArr(u.CipherKeys.PassKey),
MaskKey: security.Uint64ArrToByteArr(u.CipherKeys.MaskKey),
Salt: u.CipherKeys.Salt,
MaxNkodeLen: int64(u.CipherKeys.MaxNKodeLen),
IdxInterface: security.IntArrToByteArr(u.Interface.IdxInterface),
SvgIDInterface: security.IntArrToByteArr(u.Interface.SvgId),
CreatedAt: sql.NullString{String: utils.TimeStamp(), Valid: true},
}
return d.enqueueWriteTx(queryFunc, params)
} }
args := []any{ func (d *SqliteDB) UpdateUserNKode(u entities.User) error {
uuid.UUID(u.Id), u.Email, renew, u.RefreshToken, uuid.UUID(u.CustomerId), queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error {
u.EncipheredPasscode.Code, u.EncipheredPasscode.Mask, u.Kp.AttrsPerKey, u.Kp.NumbOfKeys, params, ok := args.(sqlc.UpdateUserParams)
security.Uint64ArrToByteArr(u.CipherKeys.AlphaKey), security.Uint64ArrToByteArr(u.CipherKeys.SetKey), if !ok {
security.Uint64ArrToByteArr(u.CipherKeys.PassKey), security.Uint64ArrToByteArr(u.CipherKeys.MaskKey), return fmt.Errorf("invalid argument type: expected UpdateUserParams")
u.CipherKeys.Salt, u.CipherKeys.MaxNKodeLen, security.IntArrToByteArr(u.Interface.IdxInterface),
security.IntArrToByteArr(u.Interface.SvgId), timeStamp(),
} }
return q.UpdateUser(ctx, params)
return d.addWriteTx(query, args)
} }
// Use the wrapped function in enqueueWriteTx
func (d *SqliteDB) UpdateUserNKode(u models.User) error { renew := 0
query := `
UPDATE user
SET renew = ?
,refresh_token = ?
,code = ?
,mask = ?
,attributes_per_key = ?
,number_of_keys = ?
,alpha_key = ?
,set_key = ?
,pass_key = ?
,mask_key = ?
,salt = ?
,max_nkode_len = ?
,idx_interface = ?
,svg_id_interface = ?
WHERE email = ? AND customer_id = ?
`
var renew int
if u.Renew { if u.Renew {
renew = 1 renew = 1
} else {
renew = 0
} }
args := []any{renew, u.RefreshToken, u.EncipheredPasscode.Code, u.EncipheredPasscode.Mask, u.Kp.AttrsPerKey, u.Kp.NumbOfKeys, security.Uint64ArrToByteArr(u.CipherKeys.AlphaKey), security.Uint64ArrToByteArr(u.CipherKeys.SetKey), security.Uint64ArrToByteArr(u.CipherKeys.PassKey), security.Uint64ArrToByteArr(u.CipherKeys.MaskKey), u.CipherKeys.Salt, u.CipherKeys.MaxNKodeLen, security.IntArrToByteArr(u.Interface.IdxInterface), security.IntArrToByteArr(u.Interface.SvgId), string(u.Email), uuid.UUID(u.CustomerId)} params := sqlc.UpdateUserParams{
Email: string(u.Email),
return d.addWriteTx(query, args) Renew: int64(renew),
RefreshToken: sql.NullString{String: u.RefreshToken, Valid: u.RefreshToken != ""},
CustomerID: uuid.UUID(u.CustomerId).String(),
Code: u.EncipheredPasscode.Code,
Mask: u.EncipheredPasscode.Mask,
AttributesPerKey: int64(u.Kp.AttrsPerKey),
NumberOfKeys: int64(u.Kp.NumbOfKeys),
AlphaKey: security.Uint64ArrToByteArr(u.CipherKeys.AlphaKey),
SetKey: security.Uint64ArrToByteArr(u.CipherKeys.SetKey),
PassKey: security.Uint64ArrToByteArr(u.CipherKeys.PassKey),
MaskKey: security.Uint64ArrToByteArr(u.CipherKeys.MaskKey),
Salt: u.CipherKeys.Salt,
MaxNkodeLen: int64(u.CipherKeys.MaxNKodeLen),
IdxInterface: security.IntArrToByteArr(u.Interface.IdxInterface),
SvgIDInterface: security.IntArrToByteArr(u.Interface.SvgId),
}
return d.enqueueWriteTx(queryFunc, params)
} }
func (d *SqliteDB) UpdateUserInterface(id models.UserId, ui models.UserInterface) error { func (d *SqliteDB) UpdateUserInterface(id models.UserId, ui entities.UserInterface) error {
query := ` queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error {
UPDATE user SET idx_interface = ?, last_login = ? WHERE id = ? params, ok := args.(sqlc.UpdateUserInterfaceParams)
` if !ok {
args := []any{security.IntArrToByteArr(ui.IdxInterface), timeStamp(), uuid.UUID(id).String()} return fmt.Errorf("invalid argument type: expected UpdateUserInterfaceParams")
}
return q.UpdateUserInterface(ctx, params)
}
params := sqlc.UpdateUserInterfaceParams{
IdxInterface: security.IntArrToByteArr(ui.IdxInterface),
LastLogin: utils.TimeStamp(),
ID: uuid.UUID(id).String(),
}
return d.addWriteTx(query, args) return d.enqueueWriteTx(queryFunc, params)
} }
func (d *SqliteDB) UpdateUserRefreshToken(id models.UserId, refreshToken string) error { func (d *SqliteDB) UpdateUserRefreshToken(id models.UserId, refreshToken string) error {
query := ` queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error {
UPDATE user SET refresh_token = ? WHERE id = ? params, ok := args.(sqlc.UpdateUserRefreshTokenParams)
` if !ok {
args := []any{refreshToken, uuid.UUID(id).String()} return fmt.Errorf("invalid argument type: expected UpdateUserRefreshToken")
}
return q.UpdateUserRefreshToken(ctx, params)
}
params := sqlc.UpdateUserRefreshTokenParams{
RefreshToken: sql.NullString{
String: refreshToken,
Valid: true,
},
ID: uuid.UUID(id).String(),
}
return d.enqueueWriteTx(queryFunc, params)
}
return d.addWriteTx(query, args) func (d *SqliteDB) RenewCustomer(renewParams sqlc.RenewCustomerParams) error {
queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error {
params, ok := args.(sqlc.RenewCustomerParams)
if !ok {
}
return q.RenewCustomer(ctx, params)
}
return d.enqueueWriteTx(queryFunc, renewParams)
} }
func (d *SqliteDB) Renew(id models.CustomerId) error { func (d *SqliteDB) Renew(id models.CustomerId) error {
// TODO: How long does a renew take? setXor, attrXor, err := d.renewCustomer(id)
customer, err := d.GetCustomer(id)
if err != nil { if err != nil {
return err return err
} }
setXor, attrXor, err := customer.RenewKeys() customerId := models.CustomerIdToString(id)
userRenewRows, err := d.queries.GetUserRenew(d.ctx, customerId)
if err != nil { if err != nil {
return err return err
} }
renewArgs := []any{security.Uint64ArrToByteArr(customer.Attributes.AttrVals), security.Uint64ArrToByteArr(customer.Attributes.SetVals), uuid.UUID(customer.Id).String()}
// TODO: replace with tx
renewQuery := `
UPDATE customer
SET attribute_values = ?, set_values = ?
WHERE id = ?;
`
userQuery := ` queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error {
SELECT params, ok := args.(sqlc.RenewUserParams)
id if !ok {
,alpha_key return fmt.Errorf("invalid argument type: expected RenewUserParams")
,set_key
,attributes_per_key
,number_of_keys
FROM user
WHERE customer_id = ?
`
tx, err := d.db.Begin()
if err != nil {
return err
} }
rows, err := tx.Query(userQuery, uuid.UUID(id).String()) return q.RenewUser(ctx, params)
for rows.Next() {
var userId string
var alphaBytes []byte
var setBytes []byte
var attrsPerKey int
var numbOfKeys int
err = rows.Scan(&userId, &alphaBytes, &setBytes, &attrsPerKey, &numbOfKeys)
if err != nil {
return err
} }
user := models.User{
Id: models.UserId{}, for _, row := range userRenewRows {
user := entities.User{
Id: models.UserIdFromString(row.ID),
CustomerId: models.CustomerId{}, CustomerId: models.CustomerId{},
Email: "", Email: "",
EncipheredPasscode: models.EncipheredNKode{}, EncipheredPasscode: models.EncipheredNKode{},
Kp: models.KeypadDimension{ Kp: entities.KeypadDimension{
AttrsPerKey: attrsPerKey, AttrsPerKey: int(row.AttributesPerKey),
NumbOfKeys: numbOfKeys, NumbOfKeys: int(row.NumberOfKeys),
}, },
CipherKeys: models.UserCipherKeys{ CipherKeys: entities.UserCipherKeys{
AlphaKey: security.ByteArrToUint64Arr(alphaBytes), AlphaKey: security.ByteArrToUint64Arr(row.AlphaKey),
SetKey: security.ByteArrToUint64Arr(setBytes), SetKey: security.ByteArrToUint64Arr(row.SetKey),
}, },
Interface: models.UserInterface{}, Interface: entities.UserInterface{},
Renew: false, Renew: false,
} }
err = user.RenewKeys(setXor, attrXor)
if err != nil { if err = user.RenewKeys(setXor, attrXor); err != nil {
return err return err
} }
renewQuery += ` params := sqlc.RenewUserParams{
UPDATE user AlphaKey: security.Uint64ArrToByteArr(user.CipherKeys.AlphaKey),
SET alpha_key = ?, set_key = ?, renew = ? SetKey: security.Uint64ArrToByteArr(user.CipherKeys.SetKey),
WHERE id = ?; Renew: 1,
` ID: uuid.UUID(user.Id).String(),
renewArgs = append(renewArgs, security.Uint64ArrToByteArr(user.CipherKeys.AlphaKey), security.Uint64ArrToByteArr(user.CipherKeys.SetKey), 1, userId)
} }
renewQuery += ` if err = d.enqueueWriteTx(queryFunc, params); err != nil {
`
err = tx.Commit()
if err != nil {
return err return err
} }
return d.addWriteTx(renewQuery, renewArgs) }
return nil
} }
func (d *SqliteDB) RefreshUserPasscode(user models.User, passcodeIdx []int, customerAttr models.CustomerAttributes) error { func (d *SqliteDB) renewCustomer(id models.CustomerId) ([]uint64, []uint64, error) {
err := user.RefreshPasscode(passcodeIdx, customerAttr) customer, err := d.GetCustomer(id)
if err != nil { if err != nil {
return nil, nil, err
}
setXor, attrXor, err := customer.RenewKeys()
if err != nil {
return nil, nil, err
}
queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error {
params, ok := args.(sqlc.RenewCustomerParams)
if !ok {
return fmt.Errorf("invalid argument type: expected RenewCustomerParams")
}
return q.RenewCustomer(ctx, params)
}
params := sqlc.RenewCustomerParams{
AttributeValues: security.Uint64ArrToByteArr(customer.Attributes.AttrVals),
SetValues: security.Uint64ArrToByteArr(customer.Attributes.SetVals),
ID: uuid.UUID(customer.Id).String(),
}
if err = d.enqueueWriteTx(queryFunc, params); err != nil {
return nil, nil, err
}
return setXor, attrXor, nil
}
func (d *SqliteDB) RefreshUserPasscode(user entities.User, passcodeIdx []int, customerAttr entities.CustomerAttributes) error {
if err := user.RefreshPasscode(passcodeIdx, customerAttr); err != nil {
return err return err
} }
query := ` queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error {
UPDATE user params, ok := args.(sqlc.RefreshUserPasscodeParams)
SET if !ok {
renew = ? return fmt.Errorf("invalid argument type: expected RefreshUserPasscodeParams")
,code = ?
,mask = ?
,alpha_key = ?
,set_key = ?
,pass_key = ?
,mask_key = ?
,salt = ?
WHERE id = ?;
`
args := []any{user.RefreshToken, 0, user.EncipheredPasscode.Code, user.EncipheredPasscode.Mask, security.Uint64ArrToByteArr(user.CipherKeys.AlphaKey), security.Uint64ArrToByteArr(user.CipherKeys.SetKey), security.Uint64ArrToByteArr(user.CipherKeys.PassKey), security.Uint64ArrToByteArr(user.CipherKeys.MaskKey), user.CipherKeys.Salt, uuid.UUID(user.Id).String()}
return d.addWriteTx(query, args)
} }
func (d *SqliteDB) GetCustomer(id models.CustomerId) (*models.Customer, error) { return q.RefreshUserPasscode(ctx, params)
tx, err := d.db.Begin()
if err != nil {
return nil, err
} }
defer func() { params := sqlc.RefreshUserPasscodeParams{
if err != nil { Renew: 0,
err = tx.Rollback() Code: user.EncipheredPasscode.Code,
if err != nil { Mask: user.EncipheredPasscode.Mask,
log.Fatal(fmt.Sprintf("Write new user won't roll back %+v", err)) AlphaKey: security.Uint64ArrToByteArr(user.CipherKeys.AlphaKey),
SetKey: security.Uint64ArrToByteArr(user.CipherKeys.SetKey),
PassKey: security.Uint64ArrToByteArr(user.CipherKeys.PassKey),
MaskKey: security.Uint64ArrToByteArr(user.CipherKeys.MaskKey),
Salt: user.CipherKeys.Salt,
ID: uuid.UUID(user.Id).String(),
} }
return d.enqueueWriteTx(queryFunc, params)
} }
}()
selectCustomer := ` func (d *SqliteDB) GetCustomer(id models.CustomerId) (*entities.Customer, error) {
SELECT customer, err := d.queries.GetCustomer(d.ctx, uuid.UUID(id).String())
max_nkode_len
,min_nkode_len
,distinct_sets
,distinct_attributes
,lock_out
,expiration
,attribute_values
,set_values
FROM customer
WHERE id = ?
`
rows, err := tx.Query(selectCustomer, uuid.UUID(id))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !rows.Next() { return &entities.Customer{
log.Printf("no new row for customer %s with err %s", id, rows.Err())
return nil, config.ErrCustomerDne
}
var maxNKodeLen int
var minNKodeLen int
var distinctSets int
var distinctAttributes int
var lockOut int
var expiration int
var attributeValues []byte
var setValues []byte
err = rows.Scan(&maxNKodeLen, &minNKodeLen, &distinctSets, &distinctAttributes, &lockOut, &expiration, &attributeValues, &setValues)
if err != nil {
return nil, err
}
customer := models.Customer{
Id: id, Id: id,
NKodePolicy: models.NKodePolicy{ NKodePolicy: models.NKodePolicy{
MaxNkodeLen: maxNKodeLen, MaxNkodeLen: int(customer.MaxNkodeLen),
MinNkodeLen: minNKodeLen, MinNkodeLen: int(customer.MinNkodeLen),
DistinctSets: distinctSets, DistinctSets: int(customer.DistinctSets),
DistinctAttributes: distinctAttributes, DistinctAttributes: int(customer.DistinctAttributes),
LockOut: lockOut, LockOut: int(customer.LockOut),
Expiration: expiration, Expiration: int(customer.Expiration),
}, },
Attributes: models.NewCustomerAttributesFromBytes(attributeValues, setValues), Attributes: entities.NewCustomerAttributesFromBytes(customer.AttributeValues, customer.SetValues),
} }, nil
if err = tx.Commit(); err != nil {
return nil, err
}
return &customer, nil
} }
func (d *SqliteDB) GetUser(email models.UserEmail, customerId models.CustomerId) (*models.User, error) { func (d *SqliteDB) GetUser(email models.UserEmail, customerId models.CustomerId) (*entities.User, error) {
tx, err := d.db.Begin() userRow, err := d.queries.GetUser(d.ctx, sqlc.GetUserParams{
Email: string(email),
CustomerID: uuid.UUID(customerId).String(),
})
if err != nil { if err != nil {
return nil, err if errors.Is(err, sql.ErrNoRows) {
}
userSelect := `
SELECT
id
,renew
,refresh_token
,code
,mask
,attributes_per_key
,number_of_keys
,alpha_key
,set_key
,pass_key
,mask_key
,salt
,max_nkode_len
,idx_interface
,svg_id_interface
FROM user
WHERE user.email = ? AND user.customer_id = ?
`
rows, err := tx.Query(userSelect, string(email), uuid.UUID(customerId).String())
if !rows.Next() {
return nil, nil return nil, nil
} }
var ( return nil, fmt.Errorf("failed to get user: %w", err)
id string
renewVal int
refreshToken string
code string
mask string
attrsPerKey int
numbOfKeys int
alphaKey []byte
setKey []byte
passKey []byte
maskKey []byte
salt []byte
maxNKodeLen int
idxInterface []byte
svgIdInterface []byte
)
err = rows.Scan(&id, &renewVal, &refreshToken, &code, &mask, &attrsPerKey, &numbOfKeys, &alphaKey, &setKey, &passKey, &maskKey, &salt, &maxNKodeLen, &idxInterface, &svgIdInterface)
userId, err := uuid.Parse(id)
if err != nil {
return nil, err
} }
var renew bool
if renewVal == 0 { kp := entities.KeypadDimension{
renew = false AttrsPerKey: int(userRow.AttributesPerKey),
} else { NumbOfKeys: int(userRow.NumberOfKeys),
}
renew := false
if userRow.Renew == 1 {
renew = true renew = true
} }
user := entities.User{
user := models.User{ Id: models.UserIdFromString(userRow.ID),
Id: models.UserId(userId),
CustomerId: customerId, CustomerId: customerId,
Email: email, Email: email,
EncipheredPasscode: models.EncipheredNKode{ EncipheredPasscode: models.EncipheredNKode{
Code: code, Code: userRow.Code,
Mask: mask, Mask: userRow.Mask,
}, },
Kp: models.KeypadDimension{ Kp: kp,
AttrsPerKey: attrsPerKey, CipherKeys: entities.UserCipherKeys{
NumbOfKeys: numbOfKeys, AlphaKey: security.ByteArrToUint64Arr(userRow.AlphaKey),
SetKey: security.ByteArrToUint64Arr(userRow.SetKey),
PassKey: security.ByteArrToUint64Arr(userRow.PassKey),
MaskKey: security.ByteArrToUint64Arr(userRow.MaskKey),
Salt: userRow.Salt,
MaxNKodeLen: int(userRow.MaxNkodeLen),
Kp: &kp,
}, },
CipherKeys: models.UserCipherKeys{ Interface: entities.UserInterface{
AlphaKey: security.ByteArrToUint64Arr(alphaKey), IdxInterface: security.ByteArrToIntArr(userRow.IdxInterface),
SetKey: security.ByteArrToUint64Arr(setKey), SvgId: security.ByteArrToIntArr(userRow.SvgIDInterface),
PassKey: security.ByteArrToUint64Arr(passKey), Kp: &kp,
MaskKey: security.ByteArrToUint64Arr(maskKey),
Salt: salt,
MaxNKodeLen: maxNKodeLen,
Kp: nil,
},
Interface: models.UserInterface{
IdxInterface: security.ByteArrToIntArr(idxInterface),
SvgId: security.ByteArrToIntArr(svgIdInterface),
Kp: nil,
}, },
Renew: renew, Renew: renew,
RefreshToken: refreshToken, RefreshToken: userRow.RefreshToken.String,
}
user.Interface.Kp = &user.Kp
user.CipherKeys.Kp = &user.Kp
if err = tx.Commit(); err != nil {
return nil, err
} }
return &user, nil return &user, nil
} }
func (d *SqliteDB) RandomSvgInterface(kp models.KeypadDimension) ([]string, error) { func (d *SqliteDB) RandomSvgInterface(kp entities.KeypadDimension) ([]string, error) {
ids, err := d.getRandomIds(kp.TotalAttrs()) ids, err := d.getRandomIds(kp.TotalAttrs())
if err != nil { if err != nil {
return nil, err return nil, err
@@ -446,7 +404,7 @@ func (d *SqliteDB) RandomSvgInterface(kp models.KeypadDimension) ([]string, erro
return d.getSvgsById(ids) return d.getSvgsById(ids)
} }
func (d *SqliteDB) RandomSvgIdxInterface(kp models.KeypadDimension) (models.SvgIdInterface, error) { func (d *SqliteDB) RandomSvgIdxInterface(kp entities.KeypadDimension) (models.SvgIdInterface, error) {
return d.getRandomIds(kp.TotalAttrs()) return d.getRandomIds(kp.TotalAttrs())
} }
@@ -455,68 +413,30 @@ func (d *SqliteDB) GetSvgStringInterface(idxs models.SvgIdInterface) ([]string,
} }
func (d *SqliteDB) getSvgsById(ids []int) ([]string, error) { func (d *SqliteDB) getSvgsById(ids []int) ([]string, error) {
tx, err := d.db.Begin()
if err != nil {
return nil, err
}
selectId := `
SELECT svg
FROM svg_icon
WHERE id = ?
`
svgs := make([]string, len(ids)) svgs := make([]string, len(ids))
for idx, id := range ids { for idx, id := range ids {
rows, err := tx.Query(selectId, id) svg, err := d.queries.GetSvgId(d.ctx, int64(id))
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !rows.Next() { svgs[idx] = svg
log.Printf("id not found: %d", id)
return nil, config.ErrSvgDne
}
if err = rows.Scan(&svgs[idx]); err != nil {
return nil, err
}
}
if err = tx.Commit(); err != nil {
return nil, err
} }
return svgs, nil return svgs, nil
} }
func (d *SqliteDB) writeToDb(query string, args []any) error { func (d *SqliteDB) enqueueWriteTx(queryFunc sqlcGeneric, args any) error {
tx, err := d.db.Begin() select {
if err != nil { case <-d.ctx.Done():
return err return errors.New("database is shutting down")
} default:
defer func() {
if err != nil {
err = tx.Rollback()
if err != nil {
log.Fatalf("fatal error: write won't roll back %+v", err)
}
}
}()
if _, err = tx.Exec(query, args...); err != nil {
return err
}
if err = tx.Commit(); err != nil {
return err
}
return nil
} }
func (d *SqliteDB) addWriteTx(query string, args []any) error { errChan := make(chan error, 1)
if d.stop {
return config.ErrStoppingDatabase
}
errChan := make(chan error)
writeTx := WriteTx{ writeTx := WriteTx{
Query: query, Query: queryFunc,
Args: args, Args: args,
ErrChan: errChan, ErrChan: errChan,
} }
d.wg.Add(1)
d.writeQueue <- writeTx d.writeQueue <- writeTx
return <-errChan return <-errChan
} }
@@ -558,7 +478,3 @@ func (d *SqliteDB) getRandomIds(count int) ([]int, error) {
return perm[:count], nil return perm[:count], nil
} }
func timeStamp() string {
return time.Now().Format(time.RFC3339)
}

View File

@@ -2,7 +2,7 @@ package db
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go-nkode/internal/api" "go-nkode/internal/entities"
"go-nkode/internal/models" "go-nkode/internal/models"
"os" "os"
"testing" "testing"
@@ -11,8 +11,9 @@ import (
func TestNewSqliteDB(t *testing.T) { func TestNewSqliteDB(t *testing.T) {
dbFile := os.Getenv("TEST_DB") dbFile := os.Getenv("TEST_DB")
// sql_driver.MakeTables(dbFile) // sql_driver.MakeTables(dbFile)
db := NewSqliteDB(dbFile) db, err := NewSqliteDB(dbFile)
defer db.CloseDb() assert.NoError(t, err)
defer db.Close()
testSignupLoginRenew(t, db) testSignupLoginRenew(t, db)
testSqliteDBRandomSvgInterface(t, db) testSqliteDBRandomSvgInterface(t, db)
@@ -24,22 +25,22 @@ func TestNewSqliteDB(t *testing.T) {
// } // }
} }
func testSignupLoginRenew(t *testing.T, db api.DbAccessor) { func testSignupLoginRenew(t *testing.T, db CustomerUserRepository) {
nkodePolicy := models.NewDefaultNKodePolicy() nkodePolicy := models.NewDefaultNKodePolicy()
customerOrig, err := models.NewCustomer(nkodePolicy) customerOrig, err := entities.NewCustomer(nkodePolicy)
assert.NoError(t, err) assert.NoError(t, err)
err = db.WriteNewCustomer(*customerOrig) err = db.CreateCustomer(*customerOrig)
assert.NoError(t, err) assert.NoError(t, err)
customer, err := db.GetCustomer(customerOrig.Id) customer, err := db.GetCustomer(customerOrig.Id)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, customerOrig, customer) assert.Equal(t, customerOrig, customer)
username := "test_user@example.com" username := "test_user@example.com"
kp := models.KeypadDefault kp := entities.KeypadDefault
passcodeIdx := []int{0, 1, 2, 3} passcodeIdx := []int{0, 1, 2, 3}
mockSvgInterface := make(models.SvgIdInterface, kp.TotalAttrs()) mockSvgInterface := make(models.SvgIdInterface, kp.TotalAttrs())
ui, err := models.NewUserInterface(&kp, mockSvgInterface) ui, err := entities.NewUserInterface(&kp, mockSvgInterface)
assert.NoError(t, err) assert.NoError(t, err)
userOrig, err := models.NewUser(*customer, username, passcodeIdx, *ui, kp) userOrig, err := entities.NewUser(*customer, username, passcodeIdx, *ui, kp)
assert.NoError(t, err) assert.NoError(t, err)
err = db.WriteNewUser(*userOrig) err = db.WriteNewUser(*userOrig)
assert.NoError(t, err) assert.NoError(t, err)
@@ -52,8 +53,8 @@ func testSignupLoginRenew(t *testing.T, db api.DbAccessor) {
} }
func testSqliteDBRandomSvgInterface(t *testing.T, db api.DbAccessor) { func testSqliteDBRandomSvgInterface(t *testing.T, db CustomerUserRepository) {
kp := models.KeypadMax kp := entities.KeypadMax
svgs, err := db.RandomSvgInterface(kp) svgs, err := db.RandomSvgInterface(kp)
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, svgs, kp.TotalAttrs()) assert.Len(t, svgs, kp.TotalAttrs())

View File

@@ -14,7 +14,7 @@ import (
"time" "time"
) )
type EmailClient interface { type Client interface {
SendEmail(Email) error SendEmail(Email) error
} }
@@ -103,22 +103,22 @@ func (s *SESClient) SendEmail(email Email) error {
return nil return nil
} }
// EmailQueue represents the email queue with rate limiting // Queue represents the email queue with rate limiting
type EmailQueue struct { type Queue struct {
stop bool stop bool
emailQueue chan Email // Email queue emailQueue chan Email // Email queue
rateLimit <-chan time.Time // Rate limiter rateLimit <-chan time.Time // Rate limiter
client EmailClient // SES client to send emails client Client // SES client to send emails
wg sync.WaitGroup // To wait for all emails to be processed wg sync.WaitGroup // To wait for all emails to be processed
FailedSendCount int FailedSendCount int
} }
// NewEmailQueue creates a new rate-limited email queue // NewEmailQueue creates a new rate-limited email queue
func NewEmailQueue(bufferSize int, emailsPerSecond int, client EmailClient) *EmailQueue { func NewEmailQueue(bufferSize int, emailsPerSecond int, client Client) *Queue {
// Create a ticker that ticks every second to limit the rate of sending emails // Create a ticker that ticks every second to limit the rate of sending emails
rateLimit := time.Tick(time.Second / time.Duration(emailsPerSecond)) rateLimit := time.Tick(time.Second / time.Duration(emailsPerSecond))
return &EmailQueue{ return &Queue{
stop: false, stop: false,
emailQueue: make(chan Email, bufferSize), emailQueue: make(chan Email, bufferSize),
rateLimit: rateLimit, rateLimit: rateLimit,
@@ -128,7 +128,7 @@ func NewEmailQueue(bufferSize int, emailsPerSecond int, client EmailClient) *Ema
} }
// AddEmail queues a new email to be sent // AddEmail queues a new email to be sent
func (q *EmailQueue) AddEmail(email Email) { func (q *Queue) AddEmail(email Email) {
if q.stop { if q.stop {
log.Printf("email %s with subject %s not add. Stopping queue", email.Recipient, email.Subject) log.Printf("email %s with subject %s not add. Stopping queue", email.Recipient, email.Subject)
return return
@@ -138,7 +138,7 @@ func (q *EmailQueue) AddEmail(email Email) {
} }
// Start begins processing the email queue with rate limiting // Start begins processing the email queue with rate limiting
func (q *EmailQueue) Start() { func (q *Queue) Start() {
q.stop = false q.stop = false
// Worker goroutine that processes emails from the queue // Worker goroutine that processes emails from the queue
go func() { go func() {
@@ -151,7 +151,7 @@ func (q *EmailQueue) Start() {
} }
// sendEmail sends an email using the SES client // sendEmail sends an email using the SES client
func (q *EmailQueue) sendEmail(email Email) { func (q *Queue) sendEmail(email Email) {
if err := q.client.SendEmail(email); err != nil { if err := q.client.SendEmail(email); err != nil {
q.FailedSendCount += 1 q.FailedSendCount += 1
log.Printf("Failed to send email to %s: %v\n", email.Recipient, err) log.Printf("Failed to send email to %s: %v\n", email.Recipient, err)
@@ -159,7 +159,7 @@ func (q *EmailQueue) sendEmail(email Email) {
} }
// Stop stops the queue after all emails have been processed // Stop stops the queue after all emails have been processed
func (q *EmailQueue) Stop() { func (q *Queue) Stop() {
q.stop = true q.stop = true
// Wait for all emails to be processed // Wait for all emails to be processed
q.wg.Wait() q.wg.Wait()

View File

@@ -22,7 +22,7 @@ func TestEmailQueue(t *testing.T) {
} }
queue.AddEmail(email) queue.AddEmail(email)
} }
// CloseDb the queue after all emails are processed // Close the queue after all emails are processed
queue.Stop() queue.Stop()
assert.Equal(t, queue.FailedSendCount, 0) assert.Equal(t, queue.FailedSendCount, 0)

View File

@@ -1,25 +1,27 @@
package models package entities
import ( import (
"github.com/google/uuid" "github.com/google/uuid"
"go-nkode/config" "go-nkode/config"
"go-nkode/internal/models"
"go-nkode/internal/security" "go-nkode/internal/security"
"go-nkode/internal/sqlc"
"go-nkode/internal/utils" "go-nkode/internal/utils"
) )
type Customer struct { type Customer struct {
Id CustomerId Id models.CustomerId
NKodePolicy NKodePolicy NKodePolicy models.NKodePolicy
Attributes CustomerAttributes Attributes CustomerAttributes
} }
func NewCustomer(nkodePolicy NKodePolicy) (*Customer, error) { func NewCustomer(nkodePolicy models.NKodePolicy) (*Customer, error) {
customerAttrs, err := NewCustomerAttributes() customerAttrs, err := NewCustomerAttributes()
if err != nil { if err != nil {
return nil, err return nil, err
} }
customer := Customer{ customer := Customer{
Id: CustomerId(uuid.New()), Id: models.CustomerId(uuid.New()),
NKodePolicy: nkodePolicy, NKodePolicy: nkodePolicy,
Attributes: *customerAttrs, Attributes: *customerAttrs,
} }
@@ -82,3 +84,19 @@ func (c *Customer) RenewKeys() ([]uint64, []uint64, error) {
} }
return setXor, attrsXor, nil return setXor, attrsXor, nil
} }
func (c *Customer) ToSqlcCreateCustomerParams() sqlc.CreateCustomerParams {
return sqlc.CreateCustomerParams{
ID: uuid.UUID(c.Id).String(),
MaxNkodeLen: int64(c.NKodePolicy.MaxNkodeLen),
MinNkodeLen: int64(c.NKodePolicy.MinNkodeLen),
DistinctSets: int64(c.NKodePolicy.DistinctSets),
DistinctAttributes: int64(c.NKodePolicy.DistinctAttributes),
LockOut: int64(c.NKodePolicy.LockOut),
Expiration: int64(c.NKodePolicy.Expiration),
AttributeValues: c.Attributes.AttrBytes(),
SetValues: c.Attributes.SetBytes(),
LastRenew: utils.TimeStamp(),
CreatedAt: utils.TimeStamp(),
}
}

View File

@@ -1,4 +1,4 @@
package models package entities
import ( import (
"go-nkode/internal/security" "go-nkode/internal/security"

View File

@@ -1,7 +1,8 @@
package models package entities
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go-nkode/internal/models"
"testing" "testing"
) )
@@ -18,10 +19,10 @@ func testNewCustomerAttributes(t *testing.T) {
func testCustomerValidKeyEntry(t *testing.T) { func testCustomerValidKeyEntry(t *testing.T) {
kp := KeypadDimension{AttrsPerKey: 10, NumbOfKeys: 9} kp := KeypadDimension{AttrsPerKey: 10, NumbOfKeys: 9}
nkodePolicy := NewDefaultNKodePolicy() nkodePolicy := models.NewDefaultNKodePolicy()
customer, err := NewCustomer(nkodePolicy) customer, err := NewCustomer(nkodePolicy)
assert.NoError(t, err) assert.NoError(t, err)
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs()) mockSvgInterface := make(models.SvgIdInterface, kp.TotalAttrs())
userInterface, err := NewUserInterface(&kp, mockSvgInterface) userInterface, err := NewUserInterface(&kp, mockSvgInterface)
assert.NoError(t, err) assert.NoError(t, err)
userEmail := "testing@example.com" userEmail := "testing@example.com"
@@ -42,10 +43,10 @@ func testCustomerValidKeyEntry(t *testing.T) {
func testCustomerIsValidNKode(t *testing.T) { func testCustomerIsValidNKode(t *testing.T) {
kp := KeypadDimension{AttrsPerKey: 10, NumbOfKeys: 7} kp := KeypadDimension{AttrsPerKey: 10, NumbOfKeys: 7}
nkodePolicy := NewDefaultNKodePolicy() nkodePolicy := models.NewDefaultNKodePolicy()
customer, err := NewCustomer(nkodePolicy) customer, err := NewCustomer(nkodePolicy)
assert.NoError(t, err) assert.NoError(t, err)
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs()) mockSvgInterface := make(models.SvgIdInterface, kp.TotalAttrs())
userInterface, err := NewUserInterface(&kp, mockSvgInterface) userInterface, err := NewUserInterface(&kp, mockSvgInterface)
assert.NoError(t, err) assert.NoError(t, err)
userEmail := "testing123@example.com" userEmail := "testing123@example.com"

View File

@@ -1,4 +1,4 @@
package models package entities
import ( import (
"go-nkode/config" "go-nkode/config"

View File

@@ -1,4 +1,4 @@
package models package entities
import ( import (
"errors" "errors"

View File

@@ -1,17 +1,18 @@
package models package entities
import ( import (
"github.com/google/uuid" "github.com/google/uuid"
"go-nkode/config" "go-nkode/config"
"go-nkode/internal/models"
"go-nkode/internal/security" "go-nkode/internal/security"
"log" "log"
) )
type User struct { type User struct {
Id UserId Id models.UserId
CustomerId CustomerId CustomerId models.CustomerId
Email UserEmail Email models.UserEmail
EncipheredPasscode EncipheredNKode EncipheredPasscode models.EncipheredNKode
Kp KeypadDimension Kp KeypadDimension
CipherKeys UserCipherKeys CipherKeys UserCipherKeys
Interface UserInterface Interface UserInterface
@@ -36,11 +37,13 @@ func (u *User) RenewKeys(setXor []uint64, attrXor []uint64) error {
func (u *User) RefreshPasscode(passcodeAttrIdx []int, customerAttributes CustomerAttributes) error { func (u *User) RefreshPasscode(passcodeAttrIdx []int, customerAttributes CustomerAttributes) error {
setVals, err := customerAttributes.SetValsForKp(u.Kp) setVals, err := customerAttributes.SetValsForKp(u.Kp)
if err != nil {
return err
}
newKeys, err := NewUserCipherKeys(&u.Kp, setVals, u.CipherKeys.MaxNKodeLen) newKeys, err := NewUserCipherKeys(&u.Kp, setVals, u.CipherKeys.MaxNKodeLen)
if err != nil { if err != nil {
return err return err
} }
encipheredPasscode, err := newKeys.EncipherNKode(passcodeAttrIdx, customerAttributes) encipheredPasscode, err := newKeys.EncipherNKode(passcodeAttrIdx, customerAttributes)
if err != nil { if err != nil {
return err return err
@@ -116,7 +119,7 @@ func ValidKeyEntry(user User, customer Customer, selectedKeys []int) ([]int, err
} }
func NewUser(customer Customer, userEmail string, passcodeIdx []int, ui UserInterface, kp KeypadDimension) (*User, error) { func NewUser(customer Customer, userEmail string, passcodeIdx []int, ui UserInterface, kp KeypadDimension) (*User, error) {
_, err := ParseEmail(userEmail) _, err := models.ParseEmail(userEmail)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -133,8 +136,8 @@ func NewUser(customer Customer, userEmail string, passcodeIdx []int, ui UserInte
return nil, err return nil, err
} }
newUser := User{ newUser := User{
Id: UserId(uuid.New()), Id: models.UserId(uuid.New()),
Email: UserEmail(userEmail), Email: models.UserEmail(userEmail),
EncipheredPasscode: *encipheredNKode, EncipheredPasscode: *encipheredNKode,
CipherKeys: *newKeys, CipherKeys: *newKeys,
Interface: ui, Interface: ui,

View File

@@ -1,9 +1,10 @@
package models package entities
import ( import (
"crypto/sha256" "crypto/sha256"
"errors" "errors"
"go-nkode/config" "go-nkode/config"
"go-nkode/internal/models"
"go-nkode/internal/security" "go-nkode/internal/security"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
@@ -166,7 +167,7 @@ func (u *UserCipherKeys) DecipherMask(mask string, setVals []uint64, passcodeLen
return passcodeSet, nil return passcodeSet, nil
} }
func (u *UserCipherKeys) EncipherNKode(passcodeAttrIdx []int, customerAttrs CustomerAttributes) (*EncipheredNKode, error) { func (u *UserCipherKeys) EncipherNKode(passcodeAttrIdx []int, customerAttrs CustomerAttributes) (*models.EncipheredNKode, error) {
attrVals, err := customerAttrs.AttrValsForKp(*u.Kp) attrVals, err := customerAttrs.AttrValsForKp(*u.Kp)
code, err := u.EncipherSaltHashCode(passcodeAttrIdx, attrVals) code, err := u.EncipherSaltHashCode(passcodeAttrIdx, attrVals)
if err != nil { if err != nil {
@@ -185,7 +186,7 @@ func (u *UserCipherKeys) EncipherNKode(passcodeAttrIdx []int, customerAttrs Cust
if err != nil { if err != nil {
return nil, err return nil, err
} }
encipheredCode := EncipheredNKode{ encipheredCode := models.EncipheredNKode{
Code: code, Code: code,
Mask: mask, Mask: mask,
} }

View File

@@ -1,19 +1,20 @@
package models package entities
import ( import (
"go-nkode/config" "go-nkode/config"
"go-nkode/internal/models"
"go-nkode/internal/security" "go-nkode/internal/security"
"go-nkode/internal/utils" "go-nkode/internal/utils"
"log" "log"
) )
type UserInterface struct { type UserInterface struct {
IdxInterface IdxInterface IdxInterface models.IdxInterface
SvgId SvgIdInterface SvgId models.SvgIdInterface
Kp *KeypadDimension Kp *KeypadDimension
} }
func NewUserInterface(kp *KeypadDimension, svgId SvgIdInterface) (*UserInterface, error) { func NewUserInterface(kp *KeypadDimension, svgId models.SvgIdInterface) (*UserInterface, error) {
idxInterface := security.IdentityArray(kp.TotalAttrs()) idxInterface := security.IdentityArray(kp.TotalAttrs())
userInterface := UserInterface{ userInterface := UserInterface{
IdxInterface: idxInterface, IdxInterface: idxInterface,

View File

@@ -1,8 +1,9 @@
package models package entities
import ( import (
"github.com/google/uuid" "github.com/google/uuid"
"go-nkode/config" "go-nkode/config"
"go-nkode/internal/models"
"go-nkode/internal/security" "go-nkode/internal/security"
py "go-nkode/internal/utils" py "go-nkode/internal/utils"
"log" "log"
@@ -10,20 +11,20 @@ import (
) )
type UserSignSession struct { type UserSignSession struct {
Id SessionId Id models.SessionId
CustomerId CustomerId CustomerId models.CustomerId
LoginUserInterface UserInterface LoginUserInterface UserInterface
Kp KeypadDimension Kp KeypadDimension
SetIdxInterface IdxInterface SetIdxInterface models.IdxInterface
ConfirmIdxInterface IdxInterface ConfirmIdxInterface models.IdxInterface
SetKeySelection KeySelection SetKeySelection models.KeySelection
UserEmail UserEmail UserEmail models.UserEmail
Reset bool Reset bool
Expire int Expire int
Colors []RGBColor Colors []models.RGBColor
} }
func NewSignupResetSession(userEmail UserEmail, kp KeypadDimension, customerId CustomerId, svgInterface SvgIdInterface, reset bool) (*UserSignSession, error) { func NewSignupResetSession(userEmail models.UserEmail, kp KeypadDimension, customerId models.CustomerId, svgInterface models.SvgIdInterface, reset bool) (*UserSignSession, error) {
loginInterface, err := NewUserInterface(&kp, svgInterface) loginInterface, err := NewUserInterface(&kp, svgInterface)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -33,7 +34,7 @@ func NewSignupResetSession(userEmail UserEmail, kp KeypadDimension, customerId C
return nil, err return nil, err
} }
session := UserSignSession{ session := UserSignSession{
Id: SessionId(uuid.New()), Id: models.SessionId(uuid.New()),
CustomerId: customerId, CustomerId: customerId,
LoginUserInterface: *loginInterface, LoginUserInterface: *loginInterface,
SetIdxInterface: signupInterface.IdxInterface, SetIdxInterface: signupInterface.IdxInterface,
@@ -48,7 +49,7 @@ func NewSignupResetSession(userEmail UserEmail, kp KeypadDimension, customerId C
return &session, nil return &session, nil
} }
func (s *UserSignSession) DeducePasscode(confirmKeyEntry KeySelection) ([]int, error) { func (s *UserSignSession) DeducePasscode(confirmKeyEntry models.KeySelection) ([]int, error) {
validEntry := py.All[int](confirmKeyEntry, func(i int) bool { validEntry := py.All[int](confirmKeyEntry, func(i int) bool {
return 0 <= i && i < s.Kp.NumbOfKeys return 0 <= i && i < s.Kp.NumbOfKeys
}) })
@@ -109,7 +110,7 @@ func (s *UserSignSession) DeducePasscode(confirmKeyEntry KeySelection) ([]int, e
return passcode, nil return passcode, nil
} }
func (s *UserSignSession) SetUserNKode(keySelection KeySelection) (IdxInterface, error) { func (s *UserSignSession) SetUserNKode(keySelection models.KeySelection) (models.IdxInterface, error) {
validKeySelection := py.All[int](keySelection, func(i int) bool { validKeySelection := py.All[int](keySelection, func(i int) bool {
return 0 <= i && i < s.Kp.NumbOfKeys return 0 <= i && i < s.Kp.NumbOfKeys
}) })
@@ -129,7 +130,7 @@ func (s *UserSignSession) SetUserNKode(keySelection KeySelection) (IdxInterface,
return s.ConfirmIdxInterface, nil return s.ConfirmIdxInterface, nil
} }
func (s *UserSignSession) getSelectedKeyVals(keySelections KeySelection, userInterface []int) ([][]int, error) { func (s *UserSignSession) getSelectedKeyVals(keySelections models.KeySelection, userInterface []int) ([][]int, error) {
signupKp := s.SignupKeypad() signupKp := s.SignupKeypad()
keypadInterface, err := security.ListToMatrix(userInterface, signupKp.AttrsPerKey) keypadInterface, err := security.ListToMatrix(userInterface, signupKp.AttrsPerKey)
if err != nil { if err != nil {
@@ -143,7 +144,7 @@ func (s *UserSignSession) getSelectedKeyVals(keySelections KeySelection, userInt
return keyVals, nil return keyVals, nil
} }
func signupInterface(baseUserInterface UserInterface, kp KeypadDimension) (*UserInterface, []RGBColor, error) { func signupInterface(baseUserInterface UserInterface, kp KeypadDimension) (*UserInterface, []models.RGBColor, error) {
// This method randomly drops sets from the base user interface so it is a square and dispersable matrix // This method randomly drops sets from the base user interface so it is a square and dispersable matrix
if kp.IsDispersable() { if kp.IsDispersable() {
return nil, nil, config.ErrKeypadIsNotDispersible return nil, nil, config.ErrKeypadIsNotDispersible
@@ -170,11 +171,11 @@ func signupInterface(baseUserInterface UserInterface, kp KeypadDimension) (*User
setIdxs = setIdxs[:kp.NumbOfKeys] setIdxs = setIdxs[:kp.NumbOfKeys]
sort.Ints(setIdxs) sort.Ints(setIdxs)
selectedSets := make([][]int, kp.NumbOfKeys) selectedSets := make([][]int, kp.NumbOfKeys)
selectedColors := make([]RGBColor, kp.NumbOfKeys) selectedColors := make([]models.RGBColor, kp.NumbOfKeys)
for idx, setIdx := range setIdxs { for idx, setIdx := range setIdxs {
selectedSets[idx] = attrSetView[setIdx] selectedSets[idx] = attrSetView[setIdx]
selectedColors[idx] = SetColors[setIdx] selectedColors[idx] = models.SetColors[setIdx]
} }
// convert set view back into key view // convert set view back into key view
selectedSets, err = security.MatrixTranspose(selectedSets) selectedSets, err = security.MatrixTranspose(selectedSets)

View File

@@ -1,7 +1,8 @@
package models package entities
import ( import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"go-nkode/internal/models"
py "go-nkode/internal/utils" py "go-nkode/internal/utils"
"testing" "testing"
) )
@@ -64,7 +65,7 @@ func TestUserInterface_RandomShuffle(t *testing.T) {
AttrsPerKey: 10, AttrsPerKey: 10,
NumbOfKeys: 8, NumbOfKeys: 8,
} }
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs()) mockSvgInterface := make(models.SvgIdInterface, kp.TotalAttrs())
userInterface, err := NewUserInterface(&kp, mockSvgInterface) userInterface, err := NewUserInterface(&kp, mockSvgInterface)
assert.NoError(t, err) assert.NoError(t, err)
userInterfaceCopy := make([]int, len(userInterface.IdxInterface)) userInterfaceCopy := make([]int, len(userInterface.IdxInterface))
@@ -87,7 +88,7 @@ func TestUserInterface_DisperseInterface(t *testing.T) {
for idx := 0; idx < 10000; idx++ { for idx := 0; idx < 10000; idx++ {
kp := KeypadDimension{AttrsPerKey: 7, NumbOfKeys: 10} kp := KeypadDimension{AttrsPerKey: 7, NumbOfKeys: 10}
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs()) mockSvgInterface := make(models.SvgIdInterface, kp.TotalAttrs())
userInterface, err := NewUserInterface(&kp, mockSvgInterface) userInterface, err := NewUserInterface(&kp, mockSvgInterface)
assert.NoError(t, err) assert.NoError(t, err)
preDispersion, err := userInterface.AttributeAdjacencyGraph() preDispersion, err := userInterface.AttributeAdjacencyGraph()
@@ -106,7 +107,7 @@ func TestUserInterface_DisperseInterface(t *testing.T) {
func TestUserInterface_PartialInterfaceShuffle(t *testing.T) { func TestUserInterface_PartialInterfaceShuffle(t *testing.T) {
kp := KeypadDimension{AttrsPerKey: 7, NumbOfKeys: 10} kp := KeypadDimension{AttrsPerKey: 7, NumbOfKeys: 10}
mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs()) mockSvgInterface := make(models.SvgIdInterface, kp.TotalAttrs())
userInterface, err := NewUserInterface(&kp, mockSvgInterface) userInterface, err := NewUserInterface(&kp, mockSvgInterface)
assert.NoError(t, err) assert.NoError(t, err)
preShuffle := userInterface.IdxInterface preShuffle := userInterface.IdxInterface

View File

@@ -1,6 +1,7 @@
package models package models
import ( import (
"fmt"
"github.com/google/uuid" "github.com/google/uuid"
"net/mail" "net/mail"
"strings" "strings"
@@ -99,10 +100,17 @@ func CustomerIdToString(customerId CustomerId) string {
type SessionId uuid.UUID type SessionId uuid.UUID
type UserId uuid.UUID type UserId uuid.UUID
func UserIdFromString(userId string) UserId {
id, err := uuid.Parse(userId)
if err != nil {
fmt.Errorf("unable to parse user id %+v", err)
}
return UserId(id)
}
func (s *SessionId) String() string { func (s *SessionId) String() string {
id := uuid.UUID(*s) id := uuid.UUID(*s)
return id.String() return id.String()
} }
type UserEmail string type UserEmail string

31
internal/sqlc/db.go Normal file
View File

@@ -0,0 +1,31 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
package sqlc
import (
"context"
"database/sql"
)
type DBTX interface {
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}
func New(db DBTX) *Queries {
return &Queries{db: db}
}
type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
}
}

50
internal/sqlc/models.go Normal file
View File

@@ -0,0 +1,50 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
package sqlc
import (
"database/sql"
)
type Customer struct {
ID string
MaxNkodeLen int64
MinNkodeLen int64
DistinctSets int64
DistinctAttributes int64
LockOut int64
Expiration int64
AttributeValues []byte
SetValues []byte
LastRenew string
CreatedAt string
}
type SvgIcon struct {
ID int64
Svg string
}
type User struct {
ID string
Email string
Renew int64
RefreshToken sql.NullString
CustomerID string
Code string
Mask string
AttributesPerKey int64
NumberOfKeys int64
AlphaKey []byte
SetKey []byte
PassKey []byte
MaskKey []byte
Salt []byte
MaxNkodeLen int64
IdxInterface []byte
SvgIDInterface []byte
LastLogin interface{}
CreatedAt sql.NullString
}

478
internal/sqlc/query.sql.go Normal file
View File

@@ -0,0 +1,478 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.27.0
// source: query.sql
package sqlc
import (
"context"
"database/sql"
)
const createCustomer = `-- name: CreateCustomer :exec
INSERT INTO customer (
id
,max_nkode_len
,min_nkode_len
,distinct_sets
,distinct_attributes
,lock_out
,expiration
,attribute_values
,set_values
,last_renew
,created_at
)
VALUES (?,?,?,?,?,?,?,?,?,?,?)
`
type CreateCustomerParams struct {
ID string
MaxNkodeLen int64
MinNkodeLen int64
DistinctSets int64
DistinctAttributes int64
LockOut int64
Expiration int64
AttributeValues []byte
SetValues []byte
LastRenew string
CreatedAt string
}
func (q *Queries) CreateCustomer(ctx context.Context, arg CreateCustomerParams) error {
_, err := q.db.ExecContext(ctx, createCustomer,
arg.ID,
arg.MaxNkodeLen,
arg.MinNkodeLen,
arg.DistinctSets,
arg.DistinctAttributes,
arg.LockOut,
arg.Expiration,
arg.AttributeValues,
arg.SetValues,
arg.LastRenew,
arg.CreatedAt,
)
return err
}
const createUser = `-- name: CreateUser :exec
INSERT INTO user (
id
,email
,renew
,refresh_token
,customer_id
,code
,mask
,attributes_per_key
,number_of_keys
,alpha_key
,set_key
,pass_key
,mask_key
,salt
,max_nkode_len
,idx_interface
,svg_id_interface
,created_at
)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
`
type CreateUserParams struct {
ID string
Email string
Renew int64
RefreshToken sql.NullString
CustomerID string
Code string
Mask string
AttributesPerKey int64
NumberOfKeys int64
AlphaKey []byte
SetKey []byte
PassKey []byte
MaskKey []byte
Salt []byte
MaxNkodeLen int64
IdxInterface []byte
SvgIDInterface []byte
CreatedAt sql.NullString
}
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) error {
_, err := q.db.ExecContext(ctx, createUser,
arg.ID,
arg.Email,
arg.Renew,
arg.RefreshToken,
arg.CustomerID,
arg.Code,
arg.Mask,
arg.AttributesPerKey,
arg.NumberOfKeys,
arg.AlphaKey,
arg.SetKey,
arg.PassKey,
arg.MaskKey,
arg.Salt,
arg.MaxNkodeLen,
arg.IdxInterface,
arg.SvgIDInterface,
arg.CreatedAt,
)
return err
}
const getCustomer = `-- name: GetCustomer :one
SELECT
max_nkode_len
,min_nkode_len
,distinct_sets
,distinct_attributes
,lock_out
,expiration
,attribute_values
,set_values
FROM customer
WHERE id = ?
`
type GetCustomerRow struct {
MaxNkodeLen int64
MinNkodeLen int64
DistinctSets int64
DistinctAttributes int64
LockOut int64
Expiration int64
AttributeValues []byte
SetValues []byte
}
func (q *Queries) GetCustomer(ctx context.Context, id string) (GetCustomerRow, error) {
row := q.db.QueryRowContext(ctx, getCustomer, id)
var i GetCustomerRow
err := row.Scan(
&i.MaxNkodeLen,
&i.MinNkodeLen,
&i.DistinctSets,
&i.DistinctAttributes,
&i.LockOut,
&i.Expiration,
&i.AttributeValues,
&i.SetValues,
)
return i, err
}
const getSvgCount = `-- name: GetSvgCount :one
SELECT COUNT(*) as count FROM svg_icon
`
func (q *Queries) GetSvgCount(ctx context.Context) (int64, error) {
row := q.db.QueryRowContext(ctx, getSvgCount)
var count int64
err := row.Scan(&count)
return count, err
}
const getSvgId = `-- name: GetSvgId :one
SELECT svg
FROM svg_icon
WHERE id = ?
`
func (q *Queries) GetSvgId(ctx context.Context, id int64) (string, error) {
row := q.db.QueryRowContext(ctx, getSvgId, id)
var svg string
err := row.Scan(&svg)
return svg, err
}
const getUser = `-- name: GetUser :one
SELECT
id
,renew
,refresh_token
,code
,mask
,attributes_per_key
,number_of_keys
,alpha_key
,set_key
,pass_key
,mask_key
,salt
,max_nkode_len
,idx_interface
,svg_id_interface
FROM user
WHERE user.email = ? AND user.customer_id = ?
`
type GetUserParams struct {
Email string
CustomerID string
}
type GetUserRow struct {
ID string
Renew int64
RefreshToken sql.NullString
Code string
Mask string
AttributesPerKey int64
NumberOfKeys int64
AlphaKey []byte
SetKey []byte
PassKey []byte
MaskKey []byte
Salt []byte
MaxNkodeLen int64
IdxInterface []byte
SvgIDInterface []byte
}
func (q *Queries) GetUser(ctx context.Context, arg GetUserParams) (GetUserRow, error) {
row := q.db.QueryRowContext(ctx, getUser, arg.Email, arg.CustomerID)
var i GetUserRow
err := row.Scan(
&i.ID,
&i.Renew,
&i.RefreshToken,
&i.Code,
&i.Mask,
&i.AttributesPerKey,
&i.NumberOfKeys,
&i.AlphaKey,
&i.SetKey,
&i.PassKey,
&i.MaskKey,
&i.Salt,
&i.MaxNkodeLen,
&i.IdxInterface,
&i.SvgIDInterface,
)
return i, err
}
const getUserRenew = `-- name: GetUserRenew :many
SELECT
id
,alpha_key
,set_key
,attributes_per_key
,number_of_keys
FROM user
WHERE customer_id = ?
`
type GetUserRenewRow struct {
ID string
AlphaKey []byte
SetKey []byte
AttributesPerKey int64
NumberOfKeys int64
}
func (q *Queries) GetUserRenew(ctx context.Context, customerID string) ([]GetUserRenewRow, error) {
rows, err := q.db.QueryContext(ctx, getUserRenew, customerID)
if err != nil {
return nil, err
}
defer rows.Close()
var items []GetUserRenewRow
for rows.Next() {
var i GetUserRenewRow
if err := rows.Scan(
&i.ID,
&i.AlphaKey,
&i.SetKey,
&i.AttributesPerKey,
&i.NumberOfKeys,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const refreshUserPasscode = `-- name: RefreshUserPasscode :exec
UPDATE user
SET
renew = ?
,code = ?
,mask = ?
,alpha_key = ?
,set_key = ?
,pass_key = ?
,mask_key = ?
,salt = ?
WHERE id = ?
`
type RefreshUserPasscodeParams struct {
Renew int64
Code string
Mask string
AlphaKey []byte
SetKey []byte
PassKey []byte
MaskKey []byte
Salt []byte
ID string
}
func (q *Queries) RefreshUserPasscode(ctx context.Context, arg RefreshUserPasscodeParams) error {
_, err := q.db.ExecContext(ctx, refreshUserPasscode,
arg.Renew,
arg.Code,
arg.Mask,
arg.AlphaKey,
arg.SetKey,
arg.PassKey,
arg.MaskKey,
arg.Salt,
arg.ID,
)
return err
}
const renewCustomer = `-- name: RenewCustomer :exec
UPDATE customer
SET attribute_values = ?, set_values = ?
WHERE id = ?
`
type RenewCustomerParams struct {
AttributeValues []byte
SetValues []byte
ID string
}
func (q *Queries) RenewCustomer(ctx context.Context, arg RenewCustomerParams) error {
_, err := q.db.ExecContext(ctx, renewCustomer, arg.AttributeValues, arg.SetValues, arg.ID)
return err
}
const renewUser = `-- name: RenewUser :exec
UPDATE user
SET alpha_key = ?, set_key = ?, renew = ?
WHERE id = ?
`
type RenewUserParams struct {
AlphaKey []byte
SetKey []byte
Renew int64
ID string
}
func (q *Queries) RenewUser(ctx context.Context, arg RenewUserParams) error {
_, err := q.db.ExecContext(ctx, renewUser,
arg.AlphaKey,
arg.SetKey,
arg.Renew,
arg.ID,
)
return err
}
const updateUser = `-- name: UpdateUser :exec
UPDATE user
SET renew = ?
,refresh_token = ?
,code = ?
,mask = ?
,attributes_per_key = ?
,number_of_keys = ?
,alpha_key = ?
,set_key = ?
,pass_key = ?
,mask_key = ?
,salt = ?
,max_nkode_len = ?
,idx_interface = ?
,svg_id_interface = ?
WHERE email = ? AND customer_id = ?
`
type UpdateUserParams struct {
Renew int64
RefreshToken sql.NullString
Code string
Mask string
AttributesPerKey int64
NumberOfKeys int64
AlphaKey []byte
SetKey []byte
PassKey []byte
MaskKey []byte
Salt []byte
MaxNkodeLen int64
IdxInterface []byte
SvgIDInterface []byte
Email string
CustomerID string
}
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error {
_, err := q.db.ExecContext(ctx, updateUser,
arg.Renew,
arg.RefreshToken,
arg.Code,
arg.Mask,
arg.AttributesPerKey,
arg.NumberOfKeys,
arg.AlphaKey,
arg.SetKey,
arg.PassKey,
arg.MaskKey,
arg.Salt,
arg.MaxNkodeLen,
arg.IdxInterface,
arg.SvgIDInterface,
arg.Email,
arg.CustomerID,
)
return err
}
const updateUserInterface = `-- name: UpdateUserInterface :exec
UPDATE user SET idx_interface = ?, last_login = ? WHERE id = ?
`
type UpdateUserInterfaceParams struct {
IdxInterface []byte
LastLogin interface{}
ID string
}
func (q *Queries) UpdateUserInterface(ctx context.Context, arg UpdateUserInterfaceParams) error {
_, err := q.db.ExecContext(ctx, updateUserInterface, arg.IdxInterface, arg.LastLogin, arg.ID)
return err
}
const updateUserRefreshToken = `-- name: UpdateUserRefreshToken :exec
UPDATE user SET refresh_token = ? WHERE id = ?
`
type UpdateUserRefreshTokenParams struct {
RefreshToken sql.NullString
ID string
}
func (q *Queries) UpdateUserRefreshToken(ctx context.Context, arg UpdateUserRefreshTokenParams) error {
_, err := q.db.ExecContext(ctx, updateUserRefreshToken, arg.RefreshToken, arg.ID)
return err
}

View File

@@ -0,0 +1,7 @@
package utils
import "time"
func TimeStamp() string {
return time.Now().Format(time.RFC3339)
}

9
sqlc.yaml Normal file
View File

@@ -0,0 +1,9 @@
version: "2"
sql:
- engine: "sqlite"
queries: "./sqlite/query.sql"
schema: "./sqlite/schema.sql"
gen:
go:
package: "sqlc"
out: "./internal/sqlc"

136
sqlite/query.sql Normal file
View File

@@ -0,0 +1,136 @@
-- name: CreateCustomer :exec
INSERT INTO customer (
id
,max_nkode_len
,min_nkode_len
,distinct_sets
,distinct_attributes
,lock_out
,expiration
,attribute_values
,set_values
,last_renew
,created_at
)
VALUES (?,?,?,?,?,?,?,?,?,?,?);
-- name: CreateUser :exec
INSERT INTO user (
id
,email
,renew
,refresh_token
,customer_id
,code
,mask
,attributes_per_key
,number_of_keys
,alpha_key
,set_key
,pass_key
,mask_key
,salt
,max_nkode_len
,idx_interface
,svg_id_interface
,created_at
)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);
-- name: UpdateUser :exec
UPDATE user
SET renew = ?
,refresh_token = ?
,code = ?
,mask = ?
,attributes_per_key = ?
,number_of_keys = ?
,alpha_key = ?
,set_key = ?
,pass_key = ?
,mask_key = ?
,salt = ?
,max_nkode_len = ?
,idx_interface = ?
,svg_id_interface = ?
WHERE email = ? AND customer_id = ?;
-- name: UpdateUserInterface :exec
UPDATE user SET idx_interface = ?, last_login = ? WHERE id = ?;
-- name: UpdateUserRefreshToken :exec
UPDATE user SET refresh_token = ? WHERE id = ?;
-- name: RenewCustomer :exec
UPDATE customer
SET attribute_values = ?, set_values = ?
WHERE id = ?;
-- name: RenewUser :exec
UPDATE user
SET alpha_key = ?, set_key = ?, renew = ?
WHERE id = ?;
-- name: RefreshUserPasscode :exec
UPDATE user
SET
renew = ?
,code = ?
,mask = ?
,alpha_key = ?
,set_key = ?
,pass_key = ?
,mask_key = ?
,salt = ?
WHERE id = ?;
-- name: GetUserRenew :many
SELECT
id
,alpha_key
,set_key
,attributes_per_key
,number_of_keys
FROM user
WHERE customer_id = ?;
-- name: GetCustomer :one
SELECT
max_nkode_len
,min_nkode_len
,distinct_sets
,distinct_attributes
,lock_out
,expiration
,attribute_values
,set_values
FROM customer
WHERE id = ?;
-- name: GetUser :one
SELECT
id
,renew
,refresh_token
,code
,mask
,attributes_per_key
,number_of_keys
,alpha_key
,set_key
,pass_key
,mask_key
,salt
,max_nkode_len
,idx_interface
,svg_id_interface
FROM user
WHERE user.email = ? AND user.customer_id = ?;
-- name: GetSvgId :one
SELECT svg
FROM svg_icon
WHERE id = ?;
-- name: GetSvgCount :one
SELECT COUNT(*) as count FROM svg_icon;

57
sqlite/schema.sql Normal file
View File

@@ -0,0 +1,57 @@
PRAGMA journal_mode=WAL;
CREATE TABLE IF NOT EXISTS customer (
id TEXT NOT NULL PRIMARY KEY
,max_nkode_len INTEGER NOT NULL
,min_nkode_len INTEGER NOT NULL
,distinct_sets INTEGER NOT NULL
,distinct_attributes INTEGER NOT NULL
,lock_out INTEGER NOT NULL
,expiration INTEGER NOT NULL
,attribute_values BLOB NOT NULL
,set_values BLOB NOT NULL
,last_renew TEXT NOT NULL
,created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS user (
id TEXT NOT NULL PRIMARY KEY
,email TEXT NOT NULL
-- first_name TEXT NOT NULL
-- last_name TEXT NOT NULL
,renew INT NOT NULL
,refresh_token TEXT
,customer_id TEXT NOT NULL
-- Enciphered Passcode
,code TEXT NOT NULL
,mask TEXT NOT NULL
-- Keypad Dimensions
,attributes_per_key INT NOT NULL
,number_of_keys INT NOT NULL
-- User Keys
,alpha_key BLOB NOT NULL
,set_key BLOB NOT NULL
,pass_key BLOB NOT NULL
,mask_key BLOB NOT NULL
,salt BLOB NOT NULL
,max_nkode_len INT NOT NULL
-- User Interface
,idx_interface BLOB NOT NULL
,svg_id_interface BLOB NOT NULL
,last_login TEXT NULL
,created_at TEXT
,FOREIGN KEY (customer_id) REFERENCES customer(id)
,UNIQUE(customer_id, email)
);
CREATE TABLE IF NOT EXISTS svg_icon (
id INTEGER PRIMARY KEY AUTOINCREMENT
,svg TEXT NOT NULL
);