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 == "" {
log.Fatalf("SQLITE_DB=/path/to/nkode.db not set")
}
db := db.NewSqliteDB(dbPath)
defer db.CloseDb()
sqlitedb, err := db.NewSqliteDB(dbPath)
if err != nil {
log.Fatalf("%v", err)
}
defer sqlitedb.Close()
sesClient := email.NewSESClient()
emailQueue := email.NewEmailQueue(emailQueueBufferSize, maxEmailsPerSecond, &sesClient)
emailQueue.Start()
defer emailQueue.Stop()
nkodeApi := api.NewNKodeAPI(db, emailQueue)
nkodeApi := api.NewNKodeAPI(sqlitedb, emailQueue)
AddDefaultCustomer(nkodeApi)
handler := api.NKodeHandler{Api: nkodeApi}

View File

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

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

View File

@@ -5,7 +5,9 @@ import (
"github.com/google/uuid"
"github.com/patrickmn/go-cache"
"go-nkode/config"
"go-nkode/internal/db"
"go-nkode/internal/email"
"go-nkode/internal/entities"
"go-nkode/internal/models"
"go-nkode/internal/security"
"log"
@@ -19,12 +21,12 @@ const (
)
type NKodeAPI struct {
Db DbAccessor
Db db.CustomerUserRepository
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{
Db: db,
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) {
newCustomer, err := models.NewCustomer(nkodePolicy)
newCustomer, err := entities.NewCustomer(nkodePolicy)
if id != nil {
newCustomer.Id = *id
}
if err != nil {
return nil, err
}
err = n.Db.WriteNewCustomer(*newCustomer)
err = n.Db.CreateCustomer(*newCustomer)
if err != nil {
return nil, err
@@ -48,7 +50,7 @@ func (n *NKodeAPI) CreateNewCustomer(nkodePolicy models.NKodePolicy, id *models.
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)
if err != nil {
return nil, err
@@ -61,7 +63,7 @@ func (n *NKodeAPI) GenerateSignupResetInterface(userEmail models.UserEmail, cust
if err != nil {
return nil, err
}
signupSession, err := models.NewSignupResetSession(userEmail, kp, customerId, svgIdxInterface, reset)
signupSession, err := entities.NewSignupResetSession(userEmail, kp, customerId, svgIdxInterface, reset)
if err != nil {
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)
return nil, config.ErrSignupSessionDNE
}
userSession, ok := session.(models.UserSignSession)
userSession, ok := session.(entities.UserSignSession)
if !ok {
// handle the case where the type assertion fails
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)
return config.ErrSignupSessionDNE
}
userSession, ok := session.(models.UserSignSession)
userSession, ok := session.(entities.UserSignSession)
if !ok {
// handle the case where the type assertion fails
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 {
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 {
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)
return nil, config.ErrUserForCustomerDNE
}
passcode, err := models.ValidKeyEntry(*user, *customer, keySelection)
passcode, err := entities.ValidKeyEntry(*user, *customer, keySelection)
if err != nil {
return nil, err
}
@@ -213,7 +215,7 @@ func (n *NKodeAPI) RenewAttributes(customerId models.CustomerId) 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) {

View File

@@ -4,6 +4,7 @@ import (
"github.com/stretchr/testify/assert"
"go-nkode/internal/db"
"go-nkode/internal/email"
"go-nkode/internal/entities"
"go-nkode/internal/models"
"go-nkode/internal/security"
"os"
@@ -16,8 +17,9 @@ func TestNKodeAPI(t *testing.T) {
dbFile := os.Getenv("TEST_DB")
db2 := db.NewSqliteDB(dbFile)
defer db2.CloseDb()
db2, err := db.NewSqliteDB(dbFile)
assert.NoError(t, err)
defer db2.Close()
testNKodeAPI(t, db2)
//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
emailsPerSec := 14
testClient := email.TestEmailClient{}
@@ -41,7 +43,7 @@ func testNKodeAPI(t *testing.T, db DbAccessor) {
userEmail := models.UserEmail("test_username" + security.GenerateRandomString(12) + "@example.com")
passcodeLen := 4
nkodePolicy := models.NewDefaultNKodePolicy()
keypadSize := models.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys}
keypadSize := entities.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys}
nkodeApi := NewNKodeAPI(db, queue)
customerId, err := nkodeApi.CreateNewCustomer(nkodePolicy, nil)
assert.NoError(t, err)
@@ -51,20 +53,20 @@ func testNKodeAPI(t *testing.T, db DbAccessor) {
sessionIdStr := signupResponse.SessionId
sessionId, err := models.SessionIdFromString(sessionIdStr)
assert.NoError(t, err)
keypadSize = models.KeypadDimension{AttrsPerKey: numbOfKeys, NumbOfKeys: numbOfKeys}
keypadSize = entities.KeypadDimension{AttrsPerKey: numbOfKeys, NumbOfKeys: numbOfKeys}
userPasscode := setInterface[:passcodeLen]
setKeySelect, err := models.SelectKeyByAttrIdx(setInterface, userPasscode, keypadSize)
setKeySelect, err := entities.SelectKeyByAttrIdx(setInterface, userPasscode, keypadSize)
assert.NoError(t, err)
confirmInterface, err := nkodeApi.SetNKode(*customerId, sessionId, setKeySelect)
assert.NoError(t, err)
confirmKeySelect, err := models.SelectKeyByAttrIdx(confirmInterface, userPasscode, keypadSize)
confirmKeySelect, err := entities.SelectKeyByAttrIdx(confirmInterface, userPasscode, keypadSize)
err = nkodeApi.ConfirmNKode(*customerId, sessionId, confirmKeySelect)
assert.NoError(t, err)
keypadSize = models.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys}
keypadSize = entities.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys}
loginInterface, err := nkodeApi.GetLoginInterface(userEmail, *customerId)
assert.NoError(t, err)
loginKeySelection, err := models.SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, keypadSize)
loginKeySelection, err := entities.SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, keypadSize)
assert.NoError(t, err)
_, err = nkodeApi.Login(*customerId, userEmail, loginKeySelection)
assert.NoError(t, err)
@@ -74,34 +76,34 @@ func testNKodeAPI(t *testing.T, db DbAccessor) {
loginInterface, err = nkodeApi.GetLoginInterface(userEmail, *customerId)
assert.NoError(t, err)
loginKeySelection, err = models.SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, keypadSize)
loginKeySelection, err = entities.SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, keypadSize)
assert.NoError(t, err)
_, err = nkodeApi.Login(*customerId, userEmail, loginKeySelection)
assert.NoError(t, err)
/// Reset nKode
attrsPerKey = 6
keypadSize = models.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys}
keypadSize = entities.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys}
resetResponse, err := nkodeApi.GenerateSignupResetInterface(userEmail, *customerId, keypadSize, true)
assert.NoError(t, err)
setInterface = resetResponse.UserIdxInterface
sessionIdStr = resetResponse.SessionId
sessionId, err = models.SessionIdFromString(sessionIdStr)
assert.NoError(t, err)
keypadSize = models.KeypadDimension{AttrsPerKey: numbOfKeys, NumbOfKeys: numbOfKeys}
keypadSize = entities.KeypadDimension{AttrsPerKey: numbOfKeys, NumbOfKeys: numbOfKeys}
userPasscode = setInterface[:passcodeLen]
setKeySelect, err = models.SelectKeyByAttrIdx(setInterface, userPasscode, keypadSize)
setKeySelect, err = entities.SelectKeyByAttrIdx(setInterface, userPasscode, keypadSize)
assert.NoError(t, err)
confirmInterface, err = nkodeApi.SetNKode(*customerId, sessionId, setKeySelect)
assert.NoError(t, err)
confirmKeySelect, err = models.SelectKeyByAttrIdx(confirmInterface, userPasscode, keypadSize)
confirmKeySelect, err = entities.SelectKeyByAttrIdx(confirmInterface, userPasscode, keypadSize)
err = nkodeApi.ConfirmNKode(*customerId, sessionId, confirmKeySelect)
assert.NoError(t, err)
keypadSize = models.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys}
keypadSize = entities.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys}
loginInterface2, err := nkodeApi.GetLoginInterface(userEmail, *customerId)
assert.NoError(t, err)
loginKeySelection, err = models.SelectKeyByAttrIdx(loginInterface2.UserIdxInterface, userPasscode, keypadSize)
loginKeySelection, err = entities.SelectKeyByAttrIdx(loginInterface2.UserIdxInterface, userPasscode, keypadSize)
assert.NoError(t, err)
_, err = nkodeApi.Login(*customerId, userEmail, loginKeySelection)
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 (
"errors"
"fmt"
"go-nkode/internal/entities"
"go-nkode/internal/models"
)
type InMemoryDb struct {
Customers map[models.CustomerId]models.Customer
Users map[models.UserId]models.User
Customers map[models.CustomerId]entities.Customer
Users map[models.UserId]entities.User
userIdMap map[string]models.UserId
}
func NewInMemoryDb() InMemoryDb {
return InMemoryDb{
Customers: make(map[models.CustomerId]models.Customer),
Users: make(map[models.UserId]models.User),
Customers: make(map[models.CustomerId]entities.Customer),
Users: make(map[models.UserId]entities.User),
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]
if !exists {
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
}
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)
userId, exists := db.userIdMap[key]
if !exists {
@@ -41,7 +42,7 @@ func (db *InMemoryDb) GetUser(username models.UserEmail, customerId models.Custo
return &user, nil
}
func (db *InMemoryDb) WriteNewCustomer(customer models.Customer) error {
func (db *InMemoryDb) CreateCustomer(customer entities.Customer) error {
_, exists := db.Customers[customer.Id]
if exists {
@@ -51,7 +52,7 @@ func (db *InMemoryDb) WriteNewCustomer(customer models.Customer) error {
return nil
}
func (db *InMemoryDb) WriteNewUser(user models.User) error {
func (db *InMemoryDb) WriteNewUser(user entities.User) error {
_, exists := db.Customers[user.CustomerId]
if !exists {
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
}
func (db *InMemoryDb) UpdateUserNKode(user models.User) error {
func (db *InMemoryDb) UpdateUserNKode(user entities.User) error {
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]
if !exists {
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
}
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)
if err != nil {
return err
@@ -116,11 +117,11 @@ func (db *InMemoryDb) RefreshUserPasscode(user models.User, passocode []int, cus
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
}
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())
for idx := range svgs {
svgs[idx] = idx

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ import (
"time"
)
type EmailClient interface {
type Client interface {
SendEmail(Email) error
}
@@ -103,22 +103,22 @@ func (s *SESClient) SendEmail(email Email) error {
return nil
}
// EmailQueue represents the email queue with rate limiting
type EmailQueue struct {
// Queue represents the email queue with rate limiting
type Queue struct {
stop bool
emailQueue chan Email // Email queue
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
FailedSendCount int
}
// 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
rateLimit := time.Tick(time.Second / time.Duration(emailsPerSecond))
return &EmailQueue{
return &Queue{
stop: false,
emailQueue: make(chan Email, bufferSize),
rateLimit: rateLimit,
@@ -128,7 +128,7 @@ func NewEmailQueue(bufferSize int, emailsPerSecond int, client EmailClient) *Ema
}
// AddEmail queues a new email to be sent
func (q *EmailQueue) AddEmail(email Email) {
func (q *Queue) AddEmail(email Email) {
if q.stop {
log.Printf("email %s with subject %s not add. Stopping queue", email.Recipient, email.Subject)
return
@@ -138,7 +138,7 @@ func (q *EmailQueue) AddEmail(email Email) {
}
// Start begins processing the email queue with rate limiting
func (q *EmailQueue) Start() {
func (q *Queue) Start() {
q.stop = false
// Worker goroutine that processes emails from the queue
go func() {
@@ -151,7 +151,7 @@ func (q *EmailQueue) Start() {
}
// 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 {
q.FailedSendCount += 1
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
func (q *EmailQueue) Stop() {
func (q *Queue) Stop() {
q.stop = true
// Wait for all emails to be processed
q.wg.Wait()

View File

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

View File

@@ -1,25 +1,27 @@
package models
package entities
import (
"github.com/google/uuid"
"go-nkode/config"
"go-nkode/internal/models"
"go-nkode/internal/security"
"go-nkode/internal/sqlc"
"go-nkode/internal/utils"
)
type Customer struct {
Id CustomerId
NKodePolicy NKodePolicy
Id models.CustomerId
NKodePolicy models.NKodePolicy
Attributes CustomerAttributes
}
func NewCustomer(nkodePolicy NKodePolicy) (*Customer, error) {
func NewCustomer(nkodePolicy models.NKodePolicy) (*Customer, error) {
customerAttrs, err := NewCustomerAttributes()
if err != nil {
return nil, err
}
customer := Customer{
Id: CustomerId(uuid.New()),
Id: models.CustomerId(uuid.New()),
NKodePolicy: nkodePolicy,
Attributes: *customerAttrs,
}
@@ -82,3 +84,19 @@ func (c *Customer) RenewKeys() ([]uint64, []uint64, error) {
}
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 (
"go-nkode/internal/security"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,9 @@
package models
package entities
import (
"github.com/google/uuid"
"go-nkode/config"
"go-nkode/internal/models"
"go-nkode/internal/security"
py "go-nkode/internal/utils"
"log"
@@ -10,20 +11,20 @@ import (
)
type UserSignSession struct {
Id SessionId
CustomerId CustomerId
Id models.SessionId
CustomerId models.CustomerId
LoginUserInterface UserInterface
Kp KeypadDimension
SetIdxInterface IdxInterface
ConfirmIdxInterface IdxInterface
SetKeySelection KeySelection
UserEmail UserEmail
SetIdxInterface models.IdxInterface
ConfirmIdxInterface models.IdxInterface
SetKeySelection models.KeySelection
UserEmail models.UserEmail
Reset bool
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)
if err != nil {
return nil, err
@@ -33,7 +34,7 @@ func NewSignupResetSession(userEmail UserEmail, kp KeypadDimension, customerId C
return nil, err
}
session := UserSignSession{
Id: SessionId(uuid.New()),
Id: models.SessionId(uuid.New()),
CustomerId: customerId,
LoginUserInterface: *loginInterface,
SetIdxInterface: signupInterface.IdxInterface,
@@ -48,7 +49,7 @@ func NewSignupResetSession(userEmail UserEmail, kp KeypadDimension, customerId C
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 {
return 0 <= i && i < s.Kp.NumbOfKeys
})
@@ -109,7 +110,7 @@ func (s *UserSignSession) DeducePasscode(confirmKeyEntry KeySelection) ([]int, e
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 {
return 0 <= i && i < s.Kp.NumbOfKeys
})
@@ -129,7 +130,7 @@ func (s *UserSignSession) SetUserNKode(keySelection KeySelection) (IdxInterface,
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()
keypadInterface, err := security.ListToMatrix(userInterface, signupKp.AttrsPerKey)
if err != nil {
@@ -143,7 +144,7 @@ func (s *UserSignSession) getSelectedKeyVals(keySelections KeySelection, userInt
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
if kp.IsDispersable() {
return nil, nil, config.ErrKeypadIsNotDispersible
@@ -170,11 +171,11 @@ func signupInterface(baseUserInterface UserInterface, kp KeypadDimension) (*User
setIdxs = setIdxs[:kp.NumbOfKeys]
sort.Ints(setIdxs)
selectedSets := make([][]int, kp.NumbOfKeys)
selectedColors := make([]RGBColor, kp.NumbOfKeys)
selectedColors := make([]models.RGBColor, kp.NumbOfKeys)
for idx, setIdx := range setIdxs {
selectedSets[idx] = attrSetView[setIdx]
selectedColors[idx] = SetColors[setIdx]
selectedColors[idx] = models.SetColors[setIdx]
}
// convert set view back into key view
selectedSets, err = security.MatrixTranspose(selectedSets)

View File

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

View File

@@ -1,6 +1,7 @@
package models
import (
"fmt"
"github.com/google/uuid"
"net/mail"
"strings"
@@ -99,10 +100,17 @@ func CustomerIdToString(customerId CustomerId) string {
type SessionId 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 {
id := uuid.UUID(*s)
return id.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
);