From 274d587472d0407015c8e84a4687a7653343b180 Mon Sep 17 00:00:00 2001 From: Donovan Date: Thu, 5 Dec 2024 16:02:04 -0600 Subject: [PATCH 01/15] underscore to dash --- compose/{coolify_compose.yaml => coolify-compose.yaml} | 0 compose/{local_compose.yaml => local-compose.yaml} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename compose/{coolify_compose.yaml => coolify-compose.yaml} (100%) rename compose/{local_compose.yaml => local-compose.yaml} (100%) diff --git a/compose/coolify_compose.yaml b/compose/coolify-compose.yaml similarity index 100% rename from compose/coolify_compose.yaml rename to compose/coolify-compose.yaml diff --git a/compose/local_compose.yaml b/compose/local-compose.yaml similarity index 100% rename from compose/local_compose.yaml rename to compose/local-compose.yaml -- 2.49.1 From c46f545827c4ae8785bd632d966882e484b70dfb Mon Sep 17 00:00:00 2001 From: Donovan Date: Thu, 5 Dec 2024 16:02:23 -0600 Subject: [PATCH 02/15] implement taskfile.yaml --- Taskfile.yaml | 29 +++++++++++++++++++++++++++++ scripts/docker_build.sh | 1 - 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 Taskfile.yaml delete mode 100644 scripts/docker_build.sh diff --git a/Taskfile.yaml b/Taskfile.yaml new file mode 100644 index 0000000..4ff06dd --- /dev/null +++ b/Taskfile.yaml @@ -0,0 +1,29 @@ +version: "3" + +vars: + compose_file: "local-compose.yaml" + cache_bust: + sh: "date +%s" + +tasks: + build: + cmds: + - docker compose -f {{.compose_file}} build --build-arg CACHE_BUST={{.cache_bust}} + up: + cmds: + - CACHE_BUST={{.cache_bust}} docker compose -f {{.compose_file}} up --build + start: + cmds: + - docker compose -f {{.compose_file}} up -d + down: + cmds: + - docker compose -f {{.compose_file}} down + clean: + cmds: + - docker system prune -f + push: + cmds: + - docker buildx build --platform linux/amd64,linux/arm64 -t registry.infra.nkode.tech/go-nkode:latest --push . + exec: + cmds: + - docker exec -it cron-nkode bash diff --git a/scripts/docker_build.sh b/scripts/docker_build.sh deleted file mode 100644 index 26b89f7..0000000 --- a/scripts/docker_build.sh +++ /dev/null @@ -1 +0,0 @@ -docker buildx build --platform linux/amd64,linux/arm64 -t registry.infra.nkode.tech/go-nkode:latest --push . -- 2.49.1 From 0f08ed311e4c44e11143df6006e9f6ef42a19b11 Mon Sep 17 00:00:00 2001 From: Donovan Date: Thu, 5 Dec 2024 16:02:56 -0600 Subject: [PATCH 03/15] move backup_sqlite.sh to cron-nkode repo --- scripts/backup_sqlite.sh | 80 ---------------------------------------- 1 file changed, 80 deletions(-) delete mode 100644 scripts/backup_sqlite.sh diff --git a/scripts/backup_sqlite.sh b/scripts/backup_sqlite.sh deleted file mode 100644 index 3c28e22..0000000 --- a/scripts/backup_sqlite.sh +++ /dev/null @@ -1,80 +0,0 @@ -#!/bin/bash - -source ~/awscli-env/bin/activate -# Usage: ./backup_script.sh -l true or ./backup_script.sh -l false - -# Parse command-line argument for local_db flag -while getopts "l:" opt; do - case $opt in - l) - LOCAL_DB=$OPTARG - ;; - *) - echo "Usage: $0 -l [true|false]" - exit 1 - ;; - esac -done - -# Validate LOCAL_DB argument -if [ "$LOCAL_DB" != "true" ] && [ "$LOCAL_DB" != "false" ]; then - echo "Error: Invalid value for -l. Must be 'true' or 'false'." - exit 1 -fi - -# Set the database path and backup destination based on LOCAL_DB value -if [ "$LOCAL_DB" = true ]; then - DB_PATH="/Users/donov/databases/nkode.db" - BACKUP_DIR="/var/tmp" -else - DB_PATH="/home/dkelly/database/nkode.db" - BACKUP_DIR="/var/tmp" -fi - -BACKUP_NAME="backup_nkode_$(date +'%Y%m%d%H%M%S').tar.gz" - -# Check if the database file exists -if [ ! -f "$DB_PATH" ]; then - echo "Error: Database file not found at $DB_PATH" - exit 1 -fi - -# Perform a WAL checkpoint to flush the WAL file to the main DB file -echo "Checkpointing WAL..." -sqlite3 "$DB_PATH" "PRAGMA wal_checkpoint(FULL);" -if [ $? -ne 0 ]; then - echo "Error: Failed to checkpoint WAL" - exit 1 -fi - -# Check if related files (.wal, .shm) exist -WAL_FILE="${DB_PATH}-wal" -SHM_FILE="${DB_PATH}-shm" - -# Create a list of files to back up (main DB, WAL, and SHM if they exist) -FILES_TO_BACKUP=("$DB_PATH") -if [ -f "$WAL_FILE" ]; then - FILES_TO_BACKUP+=("$WAL_FILE") -fi -if [ -f "$SHM_FILE" ]; then - FILES_TO_BACKUP+=("$SHM_FILE") -fi - -# Create a tar.gz archive of the database and related files -echo "Creating backup..." -tar -czf "$BACKUP_DIR/$BACKUP_NAME" "${FILES_TO_BACKUP[@]}" -if [ $? -eq 0 ]; then - echo "Backup successful: $BACKUP_DIR/$BACKUP_NAME" -else - echo "Error: Backup failed" - exit 1 -fi - -# Upload backup to S3 -aws s3 cp "$BACKUP_DIR/$BACKUP_NAME" s3://nkode-db-backup -if [ $? -eq 0 ]; then - echo "Backup uploaded to S3 successfully." -else - echo "Error: Failed to upload backup to S3." - exit 1 -fi -- 2.49.1 From 342436681bd0ed87c75e44d5ce95e9bfcba0422f Mon Sep 17 00:00:00 2001 From: Donovan Date: Thu, 5 Dec 2024 16:12:02 -0600 Subject: [PATCH 04/15] fix go build --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 18ccabd..d7fdd62 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ RUN go mod download COPY . . # Build the application -RUN go build +RUN go build -o go-nkode ./cmd # Stage 2: Runtime FROM debian:bookworm-slim -- 2.49.1 From 1bec3bbbf287cef4fd58239f7f30ca2edf64f686 Mon Sep 17 00:00:00 2001 From: Donovan Date: Fri, 6 Dec 2024 09:55:04 -0600 Subject: [PATCH 05/15] add cert authority to dockerfile --- Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Dockerfile b/Dockerfile index d7fdd62..aa6101f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,6 +24,10 @@ RUN go build -o go-nkode ./cmd # Stage 2: Runtime FROM debian:bookworm-slim +RUN apt-get update && \ + apt-get install -y --no-install-recommends ca-certificates && \ + rm -rf /var/lib/apt/lists/* \ + #ENV FRONTEND_HOST=https://app.nkode.tech #ENV FRONTEND_HOST=http://localhost:8090 ENV SVG_DIR=/app/data/icons -- 2.49.1 From 2df45ffd087e7466e4e4fe91c22641931ebda1e2 Mon Sep 17 00:00:00 2001 From: Donovan Date: Fri, 6 Dec 2024 09:56:36 -0600 Subject: [PATCH 06/15] add cert authority to dockerfile --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index aa6101f..2fcdf4f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,13 +26,14 @@ FROM debian:bookworm-slim RUN apt-get update && \ apt-get install -y --no-install-recommends ca-certificates && \ - rm -rf /var/lib/apt/lists/* \ + rm -rf /var/lib/apt/lists/* #ENV FRONTEND_HOST=https://app.nkode.tech #ENV FRONTEND_HOST=http://localhost:8090 ENV SVG_DIR=/app/data/icons ENV DB_PATH=/app/data/sqlite/nkode.db ENV SQLITE_DB=/app/data/sqlite/nkode.db + # Set the working directory inside the runtime container WORKDIR /app -- 2.49.1 From ea8673ea5a8fe25691bf7869f814df4f71ea06eb Mon Sep 17 00:00:00 2001 From: Donovan Date: Fri, 6 Dec 2024 10:06:21 -0600 Subject: [PATCH 07/15] modify ca install --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 2fcdf4f..f2dff3b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,8 +24,10 @@ RUN go build -o go-nkode ./cmd # Stage 2: Runtime FROM debian:bookworm-slim +# Install ca-certificates and update them RUN apt-get update && \ apt-get install -y --no-install-recommends ca-certificates && \ + update-ca-certificates && \ rm -rf /var/lib/apt/lists/* #ENV FRONTEND_HOST=https://app.nkode.tech -- 2.49.1 From e45924cb508d19a9c5412aebe0c6049661dd0ca5 Mon Sep 17 00:00:00 2001 From: Donovan Date: Fri, 6 Dec 2024 10:36:57 -0600 Subject: [PATCH 08/15] update local-compose.yaml --- Taskfile.yaml | 9 +++------ compose/local-compose.yaml | 7 ++++++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Taskfile.yaml b/Taskfile.yaml index 4ff06dd..27838ba 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -1,20 +1,17 @@ version: "3" vars: - compose_file: "local-compose.yaml" + compose_file: "./compose/local-compose.yaml" cache_bust: sh: "date +%s" tasks: build: cmds: - - docker compose -f {{.compose_file}} build --build-arg CACHE_BUST={{.cache_bust}} + - docker compose -f {{.compose_file}} build --no-cache up: cmds: - - CACHE_BUST={{.cache_bust}} docker compose -f {{.compose_file}} up --build - start: - cmds: - - docker compose -f {{.compose_file}} up -d + - docker compose -f {{.compose_file}} up down: cmds: - docker compose -f {{.compose_file}} down diff --git a/compose/local-compose.yaml b/compose/local-compose.yaml index e7049d2..905f113 100644 --- a/compose/local-compose.yaml +++ b/compose/local-compose.yaml @@ -1,7 +1,9 @@ services: go-nkode: container_name: go-nkode - image: registry.infra.nkode.tech/go-nkode + build: + context: ../ + dockerfile: Dockerfile volumes: - /var/go-nkode/sqlite:/app/data/sqlite - /var/go-nkode/icons:/app/data/icons @@ -9,5 +11,8 @@ services: environment: - JWT_SECRET=0123456789 - FRONTEND_HOST=http://localhost:8090 + - AWS_ACCESS_KEY_ID=AKIA5VCWLAGKV3RBVE7G + - AWS_REGION=us-east-1 + - AWS_SECRET_ACCESS_KEY=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 ports: - "8070:8080" \ No newline at end of file -- 2.49.1 From 01f9afb11c62de578aef222067e1b63579f11294 Mon Sep 17 00:00:00 2001 From: Donovan Date: Fri, 6 Dec 2024 12:56:20 -0600 Subject: [PATCH 09/15] add sql and swag tasks --- Taskfile.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Taskfile.yaml b/Taskfile.yaml index 27838ba..fef6d45 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -24,3 +24,9 @@ tasks: exec: cmds: - docker exec -it cron-nkode bash + sql: + cmds: + - sqlc generate + swag: + cmds: + - swag init --dir ./cmd \ No newline at end of file -- 2.49.1 From 70dee239188087c87984698588a404cc886e5175 Mon Sep 17 00:00:00 2001 From: Donovan Date: Fri, 6 Dec 2024 12:56:41 -0600 Subject: [PATCH 10/15] only shuffle after successful login --- internal/api/nkode_api.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/internal/api/nkode_api.go b/internal/api/nkode_api.go index cf0f581..d604e22 100644 --- a/internal/api/nkode_api.go +++ b/internal/api/nkode_api.go @@ -153,14 +153,6 @@ func (n *NKodeAPI) GetLoginInterface(userEmail models.UserEmail, customerId mode log.Printf("user %s for customer %s dne", userEmail, customerId) return nil, config.ErrUserForCustomerDNE } - err = user.Interface.PartialInterfaceShuffle() - if err != nil { - return nil, err - } - err = n.Db.UpdateUserInterface(user.Id, user.Interface) - if err != nil { - return nil, err - } svgInterface, err := n.Db.GetSvgStringInterface(user.Interface.SvgId) if err != nil { return nil, err @@ -203,8 +195,13 @@ func (n *NKodeAPI) Login(customerId models.CustomerId, userEmail models.UserEmai if err != nil { return nil, err } - err = n.Db.UpdateUserRefreshToken(user.Id, jwtToken.RefreshToken) - if err != nil { + if err = n.Db.UpdateUserRefreshToken(user.Id, jwtToken.RefreshToken); err != nil { + return nil, err + } + if err = user.Interface.PartialInterfaceShuffle(); err != nil { + return nil, err + } + if err = n.Db.UpdateUserInterface(user.Id, user.Interface); err != nil { return nil, err } return &jwtToken, nil -- 2.49.1 From 9ec8864122d07036d2140a0931280e53289b7450 Mon Sep 17 00:00:00 2001 From: Donovan Date: Fri, 6 Dec 2024 12:57:20 -0600 Subject: [PATCH 11/15] rename partial shuffle to login shuffle --- internal/api/nkode_api.go | 2 +- internal/entities/user.go | 2 +- internal/entities/user_interface.go | 2 +- internal/entities/user_test.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/api/nkode_api.go b/internal/api/nkode_api.go index d604e22..fb280a4 100644 --- a/internal/api/nkode_api.go +++ b/internal/api/nkode_api.go @@ -198,7 +198,7 @@ func (n *NKodeAPI) Login(customerId models.CustomerId, userEmail models.UserEmai if err = n.Db.UpdateUserRefreshToken(user.Id, jwtToken.RefreshToken); err != nil { return nil, err } - if err = user.Interface.PartialInterfaceShuffle(); err != nil { + if err = user.Interface.LoginShuffle(); err != nil { return nil, err } if err = n.Db.UpdateUserInterface(user.Id, user.Interface); err != nil { diff --git a/internal/entities/user.go b/internal/entities/user.go index a4ae96f..ef895a8 100644 --- a/internal/entities/user.go +++ b/internal/entities/user.go @@ -56,7 +56,7 @@ func (u *User) RefreshPasscode(passcodeAttrIdx []int, customerAttributes Custome } func (u *User) GetLoginInterface() ([]int, error) { - err := u.Interface.PartialInterfaceShuffle() + err := u.Interface.LoginShuffle() if err != nil { return nil, err diff --git a/internal/entities/user_interface.go b/internal/entities/user_interface.go index 0a691e8..ea34a86 100644 --- a/internal/entities/user_interface.go +++ b/internal/entities/user_interface.go @@ -140,7 +140,7 @@ func (u *UserInterface) AttributeAdjacencyGraph() (map[int]utils.Set[int], error return graph, nil } -func (u *UserInterface) PartialInterfaceShuffle() error { +func (u *UserInterface) LoginShuffle() error { err := u.shuffleKeys() if err != nil { return err diff --git a/internal/entities/user_test.go b/internal/entities/user_test.go index 9d93c5c..3b64a7b 100644 --- a/internal/entities/user_test.go +++ b/internal/entities/user_test.go @@ -111,7 +111,7 @@ func TestUserInterface_PartialInterfaceShuffle(t *testing.T) { userInterface, err := NewUserInterface(&kp, mockSvgInterface) assert.NoError(t, err) preShuffle := userInterface.IdxInterface - err = userInterface.PartialInterfaceShuffle() + err = userInterface.LoginShuffle() assert.NoError(t, err) postShuffle := userInterface.IdxInterface -- 2.49.1 From bd06c68ab2363012aa872045f2d8c0508ae5ba31 Mon Sep 17 00:00:00 2001 From: Donovan Date: Fri, 6 Dec 2024 13:37:14 -0600 Subject: [PATCH 12/15] fix loginShuffle implementation --- internal/entities/user_interface.go | 33 +++++++++++------------------ 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/internal/entities/user_interface.go b/internal/entities/user_interface.go index ea34a86..78f7377 100644 --- a/internal/entities/user_interface.go +++ b/internal/entities/user_interface.go @@ -141,39 +141,30 @@ func (u *UserInterface) AttributeAdjacencyGraph() (map[int]utils.Set[int], error } func (u *UserInterface) LoginShuffle() error { - err := u.shuffleKeys() - if err != nil { + if err := u.shuffleKeys(); err != nil { return err } - numbOfSelectedSets := u.Kp.AttrsPerKey / 2 - if u.Kp.AttrsPerKey&1 == 1 { - numbOfSelectedSets += security.Choice[int]([]int{0, 1}) + keypadSet1, err := u.InterfaceMatrix() + if err = u.shuffleKeys(); err != nil { + return err } + keypadSet2, err := u.InterfaceMatrix() + numbOfSelectedSets := u.Kp.AttrsPerKey / 2 setIdxs, err := security.RandomPermutation(u.Kp.AttrsPerKey) if err != nil { return err } selectedSets := utils.NewSetFromSlice[int](setIdxs[:numbOfSelectedSets]) - keypadSetView, err := u.SetViewMatrix() - if err != nil { - return err - } - interfaceBySet := make([][]int, u.Kp.AttrsPerKey) - for idx, attrs := range keypadSetView { - if selectedSets.Contains(idx) { - err = security.FisherYatesShuffle[int](&attrs) - if err != nil { - return err + for keyIdx, key := range keypadSet1 { + for idx, attrIdx := range key { + if selectedSets.Contains(attrIdx) { + keypadSet1[keyIdx][idx] = keypadSet2[keyIdx][idx] } } - interfaceBySet[idx] = attrs } - keypadView, err := security.MatrixTranspose(interfaceBySet) - if err != nil { - return err - } - u.IdxInterface = security.MatrixToList(keypadView) + + u.IdxInterface = security.MatrixToList(keypadSet1) return nil } -- 2.49.1 From fa5b643e317349ab86d3143625491f9e77802a60 Mon Sep 17 00:00:00 2001 From: Donovan Date: Fri, 6 Dec 2024 14:08:52 -0600 Subject: [PATCH 13/15] fix shuffle --- internal/entities/user.go | 5 ----- internal/entities/user_interface.go | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/internal/entities/user.go b/internal/entities/user.go index ef895a8..0754f05 100644 --- a/internal/entities/user.go +++ b/internal/entities/user.go @@ -56,11 +56,6 @@ func (u *User) RefreshPasscode(passcodeAttrIdx []int, customerAttributes Custome } func (u *User) GetLoginInterface() ([]int, error) { - err := u.Interface.LoginShuffle() - - if err != nil { - return nil, err - } return u.Interface.IdxInterface, nil } diff --git a/internal/entities/user_interface.go b/internal/entities/user_interface.go index 78f7377..3b2d966 100644 --- a/internal/entities/user_interface.go +++ b/internal/entities/user_interface.go @@ -157,8 +157,8 @@ func (u *UserInterface) LoginShuffle() error { selectedSets := utils.NewSetFromSlice[int](setIdxs[:numbOfSelectedSets]) for keyIdx, key := range keypadSet1 { - for idx, attrIdx := range key { - if selectedSets.Contains(attrIdx) { + for idx := range key { + if selectedSets.Contains(idx) { keypadSet1[keyIdx][idx] = keypadSet2[keyIdx][idx] } } -- 2.49.1 From 2f42f0c42e8c47ede6e335efc12c46eb2987d7bf Mon Sep 17 00:00:00 2001 From: Donovan Date: Sun, 29 Dec 2024 14:03:56 -0600 Subject: [PATCH 14/15] refactor with sugar-n-spice --- go.mod | 7 ++- go.sum | 4 ++ internal/entities/customer.go | 5 +- internal/entities/keypad_dimension.go | 6 +-- internal/entities/user_interface.go | 10 ++-- internal/entities/user_signup_session.go | 11 +++-- internal/entities/user_test.go | 6 +-- internal/security/util.go | 6 +-- internal/utils/hashset.go | 63 ------------------------ internal/utils/hashset_test.go | 35 ------------- internal/utils/py-builtin.go | 11 ----- 11 files changed, 30 insertions(+), 134 deletions(-) delete mode 100644 internal/utils/hashset.go delete mode 100644 internal/utils/hashset_test.go delete mode 100644 internal/utils/py-builtin.go diff --git a/go.mod b/go.mod index 1b11cd1..4c5e129 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module go-nkode -go 1.22.0 - -toolchain go1.23.0 +go 1.23.0 require ( github.com/aws/aws-sdk-go-v2 v1.31.0 @@ -12,7 +10,7 @@ require ( github.com/google/uuid v1.6.0 github.com/mattn/go-sqlite3 v1.14.22 github.com/patrickmn/go-cache v2.1.0+incompatible - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/swaggo/http-swagger v1.3.4 github.com/swaggo/swag v1.16.4 github.com/swaggo/swag/example/celler v0.0.0-20241025062444-99698582709d @@ -20,6 +18,7 @@ require ( ) require ( + github.com/DonovanKelly/sugar-n-spice v1.0.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.35 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.14 // indirect diff --git a/go.sum b/go.sum index b061348..7eb4cf7 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/DonovanKelly/sugar-n-spice v1.0.1 h1:VsybiCHSziAqyPtbYF6GtkiJYYECWMHKN+EyEa6UVpA= +github.com/DonovanKelly/sugar-n-spice v1.0.1/go.mod h1:/HQWoablLFCwsa4gwfzVBu80cI5A3dyO1uCiB11sup0= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/aws/aws-sdk-go-v2 v1.31.0 h1:3V05LbxTSItI5kUqNwhJrrrY1BAXxXt0sN0l72QmG5U= @@ -116,6 +118,8 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww= diff --git a/internal/entities/customer.go b/internal/entities/customer.go index 5ae8fad..fc6b87c 100644 --- a/internal/entities/customer.go +++ b/internal/entities/customer.go @@ -1,6 +1,7 @@ package entities import ( + "github.com/DonovanKelly/sugar-n-spice/set" "github.com/google/uuid" "go-nkode/config" "go-nkode/internal/models" @@ -38,8 +39,8 @@ func (c *Customer) IsValidNKode(kp KeypadDimension, passcodeAttrIdx []int) error if validIdx := kp.ValidateAttributeIndices(passcodeAttrIdx); !validIdx { return config.ErrInvalidNKodeIdx } - passcodeSetVals := make(utils.Set[uint64]) - passcodeAttrVals := make(utils.Set[uint64]) + passcodeSetVals := make(set.Set[uint64]) + passcodeAttrVals := make(set.Set[uint64]) attrVals, err := c.Attributes.AttrValsForKp(kp) if err != nil { return err diff --git a/internal/entities/keypad_dimension.go b/internal/entities/keypad_dimension.go index 3ffd8e4..7bac8a0 100644 --- a/internal/entities/keypad_dimension.go +++ b/internal/entities/keypad_dimension.go @@ -1,8 +1,8 @@ package entities import ( + "github.com/DonovanKelly/sugar-n-spice/all" "go-nkode/config" - py "go-nkode/internal/utils" ) type KeypadDimension struct { @@ -26,13 +26,13 @@ func (kp *KeypadDimension) IsValidKeypadDimension() error { } func (kp *KeypadDimension) ValidKeySelections(selectedKeys []int) bool { - return py.All[int](selectedKeys, func(idx int) bool { + return all.All[int](selectedKeys, func(idx int) bool { return 0 <= idx && idx < kp.NumbOfKeys }) } func (kp *KeypadDimension) ValidateAttributeIndices(attrIndicies []int) bool { - return py.All[int](attrIndicies, func(i int) bool { + return all.All[int](attrIndicies, func(i int) bool { return i >= 0 && i < kp.TotalAttrs() }) } diff --git a/internal/entities/user_interface.go b/internal/entities/user_interface.go index 3b2d966..50ecf88 100644 --- a/internal/entities/user_interface.go +++ b/internal/entities/user_interface.go @@ -1,10 +1,10 @@ package entities import ( + "github.com/DonovanKelly/sugar-n-spice/set" "go-nkode/config" "go-nkode/internal/models" "go-nkode/internal/security" - "go-nkode/internal/utils" "log" ) @@ -122,15 +122,15 @@ func (u *UserInterface) randomAttributeRotation() error { return nil } -func (u *UserInterface) AttributeAdjacencyGraph() (map[int]utils.Set[int], error) { +func (u *UserInterface) AttributeAdjacencyGraph() (map[int]set.Set[int], error) { interfaceKeypad, err := u.InterfaceMatrix() if err != nil { return nil, err } - graph := make(map[int]utils.Set[int]) + graph := make(map[int]set.Set[int]) for _, key := range interfaceKeypad { - keySet := utils.NewSetFromSlice(key) + keySet := set.NewSetFromSlice(key) for _, attr := range key { attrAdjacency := keySet.Copy() attrAdjacency.Remove(attr) @@ -154,7 +154,7 @@ func (u *UserInterface) LoginShuffle() error { if err != nil { return err } - selectedSets := utils.NewSetFromSlice[int](setIdxs[:numbOfSelectedSets]) + selectedSets := set.NewSetFromSlice[int](setIdxs[:numbOfSelectedSets]) for keyIdx, key := range keypadSet1 { for idx := range key { diff --git a/internal/entities/user_signup_session.go b/internal/entities/user_signup_session.go index de46146..4d9f28f 100644 --- a/internal/entities/user_signup_session.go +++ b/internal/entities/user_signup_session.go @@ -1,11 +1,12 @@ package entities import ( + "github.com/DonovanKelly/sugar-n-spice/all" + "github.com/DonovanKelly/sugar-n-spice/set" "github.com/google/uuid" "go-nkode/config" "go-nkode/internal/models" "go-nkode/internal/security" - py "go-nkode/internal/utils" "log" "sort" ) @@ -50,7 +51,7 @@ func NewSignupResetSession(userEmail models.UserEmail, kp KeypadDimension, custo } func (s *UserSignSession) DeducePasscode(confirmKeyEntry models.KeySelection) ([]int, error) { - validEntry := py.All[int](confirmKeyEntry, func(i int) bool { + validEntry := all.All[int](confirmKeyEntry, func(i int) bool { return 0 <= i && i < s.Kp.NumbOfKeys }) @@ -93,8 +94,8 @@ func (s *UserSignSession) DeducePasscode(confirmKeyEntry models.KeySelection) ([ passcode := make([]int, passcodeLen) for idx := 0; idx < passcodeLen; idx++ { - setKey := py.NewSetFromSlice[int](setKeyVals[idx]) - confirmKey := py.NewSetFromSlice[int](confirmKeyVals[idx]) + setKey := set.NewSetFromSlice[int](setKeyVals[idx]) + confirmKey := set.NewSetFromSlice[int](confirmKeyVals[idx]) intersection := setKey.Intersect(confirmKey) if intersection.Size() < 1 { log.Printf("set and confirm do not intersect at index %d", idx) @@ -111,7 +112,7 @@ func (s *UserSignSession) DeducePasscode(confirmKeyEntry models.KeySelection) ([ } func (s *UserSignSession) SetUserNKode(keySelection models.KeySelection) (models.IdxInterface, error) { - validKeySelection := py.All[int](keySelection, func(i int) bool { + validKeySelection := all.All[int](keySelection, func(i int) bool { return 0 <= i && i < s.Kp.NumbOfKeys }) if !validKeySelection { diff --git a/internal/entities/user_test.go b/internal/entities/user_test.go index 3b64a7b..9eab167 100644 --- a/internal/entities/user_test.go +++ b/internal/entities/user_test.go @@ -1,9 +1,9 @@ package entities import ( + "github.com/DonovanKelly/sugar-n-spice/all" "github.com/stretchr/testify/assert" "go-nkode/internal/models" - py "go-nkode/internal/utils" "testing" ) @@ -120,12 +120,12 @@ func TestUserInterface_PartialInterfaceShuffle(t *testing.T) { shuffleCompare[idx] = val == postShuffle[idx] } - allTrue := py.All[bool](shuffleCompare, func(n bool) bool { + allTrue := all.All[bool](shuffleCompare, func(n bool) bool { return n == true }) assert.False(t, allTrue) - allFalse := py.All[bool](shuffleCompare, func(n bool) bool { + allFalse := all.All[bool](shuffleCompare, func(n bool) bool { return n == false }) diff --git a/internal/security/util.go b/internal/security/util.go index f40193a..43a3593 100644 --- a/internal/security/util.go +++ b/internal/security/util.go @@ -6,7 +6,7 @@ import ( "encoding/binary" "encoding/hex" "errors" - "go-nkode/internal/utils" + "github.com/DonovanKelly/sugar-n-spice/set" "log" "math/big" r "math/rand" @@ -84,7 +84,7 @@ func GenerateRandomNonRepeatingUint64(listLen int) ([]uint64, error) { if listLen > int(1)<<32 { return nil, ErrRandNonRepeatingUint64 } - listSet := make(utils.Set[uint64]) + listSet := make(set.Set[uint64]) for { if listSet.Size() == listLen { break @@ -104,7 +104,7 @@ func GenerateRandomNonRepeatingInt(listLen int) ([]int, error) { if listLen > int(1)<<31 { return nil, ErrRandNonRepeatingInt } - listSet := make(utils.Set[int]) + listSet := make(set.Set[int]) for { if listSet.Size() == listLen { break diff --git a/internal/utils/hashset.go b/internal/utils/hashset.go deleted file mode 100644 index dc5a9aa..0000000 --- a/internal/utils/hashset.go +++ /dev/null @@ -1,63 +0,0 @@ -package utils - -type Set[T comparable] map[T]struct{} - -func (s *Set[T]) Add(element T) { - (*s)[element] = struct{}{} -} - -func (s *Set[T]) Remove(element T) { - delete(*s, element) -} - -func (s *Set[T]) Contains(element T) bool { - _, exists := (*s)[element] - return exists -} - -func (s *Set[T]) Size() int { - return len(*s) -} - -func (s *Set[T]) ToSlice() []T { - list := make([]T, 0, len(*s)) - for key := range *s { - list = append(list, key) - } - return list -} - -func NewSetFromSlice[T comparable](slice []T) Set[T] { - set := make(Set[T]) - for _, val := range slice { - set.Add(val) - } - return set -} - -func (s *Set[T]) Copy() Set[T] { - newSet := make(Set[T]) - for key, val := range *s { - newSet[key] = val - } - return newSet -} - -func (s *Set[T]) IsDisjoint(otherSet Set[T]) bool { - for attr := range *s { - if otherSet.Contains(attr) { - return false - } - } - return true -} - -func (s *Set[T]) Intersect(otherSet Set[T]) Set[T] { - intersect := make(Set[T]) - for val := range *s { - if otherSet.Contains(val) { - intersect.Add(val) - } - } - return intersect -} diff --git a/internal/utils/hashset_test.go b/internal/utils/hashset_test.go deleted file mode 100644 index 85b9c40..0000000 --- a/internal/utils/hashset_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package utils - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestSet(t *testing.T) { - intSet := make(Set[int]) - intSet.Add(1) - intSet.Add(2) - assert.EqualValues(t, intSet.Size(), 2) - intSet.Add(3) - intSet.Add(3) - assert.EqualValues(t, intSet.Size(), 3) - intSet.Remove(2) - assert.EqualValues(t, intSet.Size(), 2) - assert.False(t, intSet.Contains(2)) - assert.True(t, intSet.Contains(1)) - - list := intSet.ToSlice() - assert.Contains(t, list, 1) - assert.Contains(t, list, 3) -} - -func TestSet_Copy(t *testing.T) { - intSet := NewSetFromSlice[int]([]int{1, 2, 3}) - - copySet := intSet.Copy() - - intSet.Remove(1) - assert.Equal(t, intSet.Size(), 2) - assert.Equal(t, copySet.Size(), 3) - -} diff --git a/internal/utils/py-builtin.go b/internal/utils/py-builtin.go deleted file mode 100644 index b613598..0000000 --- a/internal/utils/py-builtin.go +++ /dev/null @@ -1,11 +0,0 @@ -package utils - -func All[T comparable](slice []T, condition func(T) bool) bool { - - for _, v := range slice { - if !condition(v) { - return false - } - } - return true -} -- 2.49.1 From f1121711451aead547e65cb3d81321798c403cc6 Mon Sep 17 00:00:00 2001 From: Donovan Date: Thu, 2 Jan 2025 17:32:33 -0600 Subject: [PATCH 15/15] refactor sqlite queue --- cmd/main.go | 35 +++- internal/api/nkode_api.go | 6 +- internal/api/nkode_api_test.go | 30 +++- internal/email/queue.go | 2 +- internal/email/queue_test.go | 2 +- .../customer_user_repository.go | 2 +- internal/{db => repository}/in_memory_db.go | 2 +- .../sqlite-init/json/academicons.json | 0 .../sqlite-init/json/akar-icons.json | 0 .../sqlite-init/json/ant-design.json | 0 .../sqlite-init/json/arcticons.json | 0 .../sqlite-init/json/basil.json | 0 .../sqlite-init/json/bitcoin-icons.json | 0 .../sqlite-init/sqlite_init.go | 0 .../sqlite_repository.go} | 158 +++++------------- .../sqlite_repository_test.go} | 27 +-- internal/sqlc/sqlite_queue.go | 93 +++++++++++ 17 files changed, 204 insertions(+), 153 deletions(-) rename internal/{db => repository}/customer_user_repository.go (97%) rename internal/{db => repository}/in_memory_db.go (99%) rename internal/{db => repository}/sqlite-init/json/academicons.json (100%) rename internal/{db => repository}/sqlite-init/json/akar-icons.json (100%) rename internal/{db => repository}/sqlite-init/json/ant-design.json (100%) rename internal/{db => repository}/sqlite-init/json/arcticons.json (100%) rename internal/{db => repository}/sqlite-init/json/basil.json (100%) rename internal/{db => repository}/sqlite-init/json/bitcoin-icons.json (100%) rename internal/{db => repository}/sqlite-init/sqlite_init.go (100%) rename internal/{db/sqlite_db.go => repository/sqlite_repository.go} (71%) rename internal/{db/sqlite_db_test.go => repository/sqlite_repository_test.go} (76%) create mode 100644 internal/sqlc/sqlite_queue.go diff --git a/cmd/main.go b/cmd/main.go index c119843..2f2f19c 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,14 +1,17 @@ package main import ( + "context" + "database/sql" "fmt" "github.com/google/uuid" httpSwagger "github.com/swaggo/http-swagger" _ "go-nkode/docs" "go-nkode/internal/api" - "go-nkode/internal/db" "go-nkode/internal/email" "go-nkode/internal/models" + "go-nkode/internal/repository" + sqliteQueue "go-nkode/internal/sqlc" "log" "net/http" "os" @@ -37,24 +40,42 @@ const ( // @securityDefinitions.apiKey ApiKeyAuth // @in header // @name Authorization - func main() { dbPath := os.Getenv("SQLITE_DB") if dbPath == "" { - log.Fatalf("SQLITE_DB=/path/to/nkode.db not set") + log.Fatal("SQLITE_DB=/path/to/nkode.db not set") } - sqlitedb, err := db.NewSqliteDB(dbPath) + + sqliteDb, err := sql.Open("sqlite3", dbPath) if err != nil { - log.Fatalf("%v", err) + log.Fatalf("failed to open database: %v", err) } - defer sqlitedb.Close() + + if err := sqliteDb.Ping(); err != nil { + log.Fatalf("failed to connect to database: %v", err) + } + + ctx := context.Background() + queue, err := sqliteQueue.NewQueue(sqliteDb, ctx) + if err != nil { + log.Fatal(err) + } + queue.Start() + + defer func(queue *sqliteQueue.Queue) { + if err := queue.Stop(); err != nil { + log.Fatal(err) + } + }(queue) sesClient := email.NewSESClient() emailQueue := email.NewEmailQueue(emailQueueBufferSize, maxEmailsPerSecond, &sesClient) emailQueue.Start() defer emailQueue.Stop() - nkodeApi := api.NewNKodeAPI(sqlitedb, emailQueue) + sqlitedb := repository.NewSqliteRepository(queue, ctx) + nkodeApi := api.NewNKodeAPI(&sqlitedb, emailQueue) + AddDefaultCustomer(nkodeApi) handler := api.NKodeHandler{Api: nkodeApi} diff --git a/internal/api/nkode_api.go b/internal/api/nkode_api.go index fb280a4..814b8c8 100644 --- a/internal/api/nkode_api.go +++ b/internal/api/nkode_api.go @@ -5,10 +5,10 @@ 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/repository" "go-nkode/internal/security" "log" "os" @@ -21,12 +21,12 @@ const ( ) type NKodeAPI struct { - Db db.CustomerUserRepository + Db repository.CustomerUserRepository SignupSessionCache *cache.Cache EmailQueue *email.Queue } -func NewNKodeAPI(db db.CustomerUserRepository, queue *email.Queue) NKodeAPI { +func NewNKodeAPI(db repository.CustomerUserRepository, queue *email.Queue) NKodeAPI { return NKodeAPI{ Db: db, EmailQueue: queue, diff --git a/internal/api/nkode_api_test.go b/internal/api/nkode_api_test.go index 8200d1c..4e28e08 100644 --- a/internal/api/nkode_api_test.go +++ b/internal/api/nkode_api_test.go @@ -1,12 +1,15 @@ package api import ( + "context" "github.com/stretchr/testify/assert" - "go-nkode/internal/db" "go-nkode/internal/email" "go-nkode/internal/entities" "go-nkode/internal/models" + "go-nkode/internal/repository" "go-nkode/internal/security" + sqlite_queue "go-nkode/internal/sqlc" + "log" "os" "testing" ) @@ -15,22 +18,31 @@ func TestNKodeAPI(t *testing.T) { //db1 := NewInMemoryDb() //testNKodeAPI(t, &db1) - dbFile := os.Getenv("TEST_DB") - - db2, err := db.NewSqliteDB(dbFile) + dbPath := os.Getenv("TEST_DB") + ctx := context.Background() + sqliteDb, err := sqlite_queue.OpenSqliteDb(dbPath) assert.NoError(t, err) - defer db2.Close() - testNKodeAPI(t, db2) - //if _, err := os.Stat(dbFile); err == nil { - // err = os.Remove(dbFile) + queue, err := sqlite_queue.NewQueue(sqliteDb, ctx) + assert.NoError(t, err) + queue.Start() + defer func(queue *sqlite_queue.Queue) { + if err := queue.Stop(); err != nil { + log.Fatal(err) + } + }(queue) + sqlitedb := repository.NewSqliteRepository(queue, ctx) + testNKodeAPI(t, &sqlitedb) + + //if _, err := os.Stat(dbPath); err == nil { + // err = os.Remove(dbPath) // assert.NoError(t, err) //} else { // assert.NoError(t, err) //} } -func testNKodeAPI(t *testing.T, db db.CustomerUserRepository) { +func testNKodeAPI(t *testing.T, db repository.CustomerUserRepository) { bufferSize := 100 emailsPerSec := 14 testClient := email.TestEmailClient{} diff --git a/internal/email/queue.go b/internal/email/queue.go index 597694b..4bd2d43 100644 --- a/internal/email/queue.go +++ b/internal/email/queue.go @@ -163,6 +163,6 @@ func (q *Queue) Stop() { q.stop = true // Wait for all emails to be processed q.wg.Wait() - // Close the email queue + // Stop the email queue close(q.emailQueue) } diff --git a/internal/email/queue_test.go b/internal/email/queue_test.go index a0733d0..9baee84 100644 --- a/internal/email/queue_test.go +++ b/internal/email/queue_test.go @@ -22,7 +22,7 @@ func TestEmailQueue(t *testing.T) { } queue.AddEmail(email) } - // Close the queue after all emails are processed + // Stop the queue after all emails are processed queue.Stop() assert.Equal(t, queue.FailedSendCount, 0) diff --git a/internal/db/customer_user_repository.go b/internal/repository/customer_user_repository.go similarity index 97% rename from internal/db/customer_user_repository.go rename to internal/repository/customer_user_repository.go index 3b59d51..3cdb620 100644 --- a/internal/db/customer_user_repository.go +++ b/internal/repository/customer_user_repository.go @@ -1,4 +1,4 @@ -package db +package repository import ( "go-nkode/internal/entities" diff --git a/internal/db/in_memory_db.go b/internal/repository/in_memory_db.go similarity index 99% rename from internal/db/in_memory_db.go rename to internal/repository/in_memory_db.go index a73e413..9869234 100644 --- a/internal/db/in_memory_db.go +++ b/internal/repository/in_memory_db.go @@ -1,4 +1,4 @@ -package db +package repository import ( "errors" diff --git a/internal/db/sqlite-init/json/academicons.json b/internal/repository/sqlite-init/json/academicons.json similarity index 100% rename from internal/db/sqlite-init/json/academicons.json rename to internal/repository/sqlite-init/json/academicons.json diff --git a/internal/db/sqlite-init/json/akar-icons.json b/internal/repository/sqlite-init/json/akar-icons.json similarity index 100% rename from internal/db/sqlite-init/json/akar-icons.json rename to internal/repository/sqlite-init/json/akar-icons.json diff --git a/internal/db/sqlite-init/json/ant-design.json b/internal/repository/sqlite-init/json/ant-design.json similarity index 100% rename from internal/db/sqlite-init/json/ant-design.json rename to internal/repository/sqlite-init/json/ant-design.json diff --git a/internal/db/sqlite-init/json/arcticons.json b/internal/repository/sqlite-init/json/arcticons.json similarity index 100% rename from internal/db/sqlite-init/json/arcticons.json rename to internal/repository/sqlite-init/json/arcticons.json diff --git a/internal/db/sqlite-init/json/basil.json b/internal/repository/sqlite-init/json/basil.json similarity index 100% rename from internal/db/sqlite-init/json/basil.json rename to internal/repository/sqlite-init/json/basil.json diff --git a/internal/db/sqlite-init/json/bitcoin-icons.json b/internal/repository/sqlite-init/json/bitcoin-icons.json similarity index 100% rename from internal/db/sqlite-init/json/bitcoin-icons.json rename to internal/repository/sqlite-init/json/bitcoin-icons.json diff --git a/internal/db/sqlite-init/sqlite_init.go b/internal/repository/sqlite-init/sqlite_init.go similarity index 100% rename from internal/db/sqlite-init/sqlite_init.go rename to internal/repository/sqlite-init/sqlite_init.go diff --git a/internal/db/sqlite_db.go b/internal/repository/sqlite_repository.go similarity index 71% rename from internal/db/sqlite_db.go rename to internal/repository/sqlite_repository.go index 16b48b4..88e2a7c 100644 --- a/internal/db/sqlite_db.go +++ b/internal/repository/sqlite_repository.go @@ -1,4 +1,4 @@ -package db +package repository import ( "context" @@ -14,82 +14,21 @@ import ( "go-nkode/internal/sqlc" "go-nkode/internal/utils" "log" - "sync" ) -const writeBufferSize = 100 - -type sqlcGeneric func(*sqlc.Queries, context.Context, any) error - -// WriteTx represents a write transaction -type WriteTx struct { - ErrChan chan error - Query sqlcGeneric - Args interface{} +type SqliteRepository struct { + Queue *sqlc.Queue + ctx context.Context } -// 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") - } - - db, err := sql.Open("sqlite3", path) - if err != nil { - return nil, fmt.Errorf("failed to open database: %w", err) - } - - 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, - writeQueue: make(chan WriteTx, writeBufferSize), - ctx: ctx, - cancel: cancel, - } - - sqldb.wg.Add(1) - go sqldb.processWriteQueue() - - return sqldb, nil -} - -// 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 NewSqliteRepository(queue *sqlc.Queue, ctx context.Context) SqliteRepository { + return SqliteRepository{ + Queue: queue, + ctx: ctx, } } -func (d *SqliteDB) Close() error { - d.cancel() - d.wg.Wait() - close(d.writeQueue) - return d.db.Close() -} - -func (d *SqliteDB) CreateCustomer(c entities.Customer) error { +func (d *SqliteRepository) CreateCustomer(c entities.Customer) error { queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error { params, ok := args.(sqlc.CreateCustomerParams) if !ok { @@ -98,10 +37,10 @@ func (d *SqliteDB) CreateCustomer(c entities.Customer) error { return q.CreateCustomer(ctx, params) } - return d.enqueueWriteTx(queryFunc, c.ToSqlcCreateCustomerParams()) + return d.Queue.EnqueueWriteTx(queryFunc, c.ToSqlcCreateCustomerParams()) } -func (d *SqliteDB) WriteNewUser(u entities.User) error { +func (d *SqliteRepository) WriteNewUser(u entities.User) error { queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error { params, ok := args.(sqlc.CreateUserParams) if !ok { @@ -109,7 +48,7 @@ func (d *SqliteDB) WriteNewUser(u entities.User) error { } return q.CreateUser(ctx, params) } - // Use the wrapped function in enqueueWriteTx + // Use the wrapped function in EnqueueWriteTx renew := 0 if u.Renew { @@ -136,10 +75,10 @@ func (d *SqliteDB) WriteNewUser(u entities.User) error { SvgIDInterface: security.IntArrToByteArr(u.Interface.SvgId), CreatedAt: sql.NullString{String: utils.TimeStamp(), Valid: true}, } - return d.enqueueWriteTx(queryFunc, params) + return d.Queue.EnqueueWriteTx(queryFunc, params) } -func (d *SqliteDB) UpdateUserNKode(u entities.User) error { +func (d *SqliteRepository) UpdateUserNKode(u entities.User) error { queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error { params, ok := args.(sqlc.UpdateUserParams) if !ok { @@ -147,7 +86,7 @@ func (d *SqliteDB) UpdateUserNKode(u entities.User) error { } return q.UpdateUser(ctx, params) } - // Use the wrapped function in enqueueWriteTx + // Use the wrapped function in EnqueueWriteTx renew := 0 if u.Renew { renew = 1 @@ -170,10 +109,10 @@ func (d *SqliteDB) UpdateUserNKode(u entities.User) error { IdxInterface: security.IntArrToByteArr(u.Interface.IdxInterface), SvgIDInterface: security.IntArrToByteArr(u.Interface.SvgId), } - return d.enqueueWriteTx(queryFunc, params) + return d.Queue.EnqueueWriteTx(queryFunc, params) } -func (d *SqliteDB) UpdateUserInterface(id models.UserId, ui entities.UserInterface) error { +func (d *SqliteRepository) 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 { @@ -187,10 +126,10 @@ func (d *SqliteDB) UpdateUserInterface(id models.UserId, ui entities.UserInterfa ID: uuid.UUID(id).String(), } - return d.enqueueWriteTx(queryFunc, params) + return d.Queue.EnqueueWriteTx(queryFunc, params) } -func (d *SqliteDB) UpdateUserRefreshToken(id models.UserId, refreshToken string) error { +func (d *SqliteRepository) UpdateUserRefreshToken(id models.UserId, refreshToken string) error { queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error { params, ok := args.(sqlc.UpdateUserRefreshTokenParams) if !ok { @@ -205,10 +144,10 @@ func (d *SqliteDB) UpdateUserRefreshToken(id models.UserId, refreshToken string) }, ID: uuid.UUID(id).String(), } - return d.enqueueWriteTx(queryFunc, params) + return d.Queue.EnqueueWriteTx(queryFunc, params) } -func (d *SqliteDB) RenewCustomer(renewParams sqlc.RenewCustomerParams) error { +func (d *SqliteRepository) RenewCustomer(renewParams sqlc.RenewCustomerParams) error { queryFunc := func(q *sqlc.Queries, ctx context.Context, args any) error { params, ok := args.(sqlc.RenewCustomerParams) if !ok { @@ -216,16 +155,16 @@ func (d *SqliteDB) RenewCustomer(renewParams sqlc.RenewCustomerParams) error { } return q.RenewCustomer(ctx, params) } - return d.enqueueWriteTx(queryFunc, renewParams) + return d.Queue.EnqueueWriteTx(queryFunc, renewParams) } -func (d *SqliteDB) Renew(id models.CustomerId) error { +func (d *SqliteRepository) Renew(id models.CustomerId) error { setXor, attrXor, err := d.renewCustomer(id) if err != nil { return err } customerId := models.CustomerIdToString(id) - userRenewRows, err := d.queries.GetUserRenew(d.ctx, customerId) + userRenewRows, err := d.Queue.Queries.GetUserRenew(d.ctx, customerId) if err != nil { return err } @@ -265,14 +204,14 @@ func (d *SqliteDB) Renew(id models.CustomerId) error { Renew: 1, ID: uuid.UUID(user.Id).String(), } - if err = d.enqueueWriteTx(queryFunc, params); err != nil { + if err = d.Queue.EnqueueWriteTx(queryFunc, params); err != nil { return err } } return nil } -func (d *SqliteDB) renewCustomer(id models.CustomerId) ([]uint64, []uint64, error) { +func (d *SqliteRepository) renewCustomer(id models.CustomerId) ([]uint64, []uint64, error) { customer, err := d.GetCustomer(id) if err != nil { return nil, nil, err @@ -295,13 +234,13 @@ func (d *SqliteDB) renewCustomer(id models.CustomerId) ([]uint64, []uint64, erro ID: uuid.UUID(customer.Id).String(), } - if err = d.enqueueWriteTx(queryFunc, params); err != nil { + if err = d.Queue.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 { +func (d *SqliteRepository) RefreshUserPasscode(user entities.User, passcodeIdx []int, customerAttr entities.CustomerAttributes) error { if err := user.RefreshPasscode(passcodeIdx, customerAttr); err != nil { return err } @@ -323,11 +262,11 @@ func (d *SqliteDB) RefreshUserPasscode(user entities.User, passcodeIdx []int, cu Salt: user.CipherKeys.Salt, ID: uuid.UUID(user.Id).String(), } - return d.enqueueWriteTx(queryFunc, params) + return d.Queue.EnqueueWriteTx(queryFunc, params) } -func (d *SqliteDB) GetCustomer(id models.CustomerId) (*entities.Customer, error) { - customer, err := d.queries.GetCustomer(d.ctx, uuid.UUID(id).String()) +func (d *SqliteRepository) GetCustomer(id models.CustomerId) (*entities.Customer, error) { + customer, err := d.Queue.Queries.GetCustomer(d.ctx, uuid.UUID(id).String()) if err != nil { return nil, err } @@ -346,8 +285,8 @@ func (d *SqliteDB) GetCustomer(id models.CustomerId) (*entities.Customer, error) }, nil } -func (d *SqliteDB) GetUser(email models.UserEmail, customerId models.CustomerId) (*entities.User, error) { - userRow, err := d.queries.GetUser(d.ctx, sqlc.GetUserParams{ +func (d *SqliteRepository) GetUser(email models.UserEmail, customerId models.CustomerId) (*entities.User, error) { + userRow, err := d.Queue.Queries.GetUser(d.ctx, sqlc.GetUserParams{ Email: string(email), CustomerID: uuid.UUID(customerId).String(), }) @@ -396,7 +335,7 @@ func (d *SqliteDB) GetUser(email models.UserEmail, customerId models.CustomerId) return &user, nil } -func (d *SqliteDB) RandomSvgInterface(kp entities.KeypadDimension) ([]string, error) { +func (d *SqliteRepository) RandomSvgInterface(kp entities.KeypadDimension) ([]string, error) { ids, err := d.getRandomIds(kp.TotalAttrs()) if err != nil { return nil, err @@ -404,18 +343,18 @@ func (d *SqliteDB) RandomSvgInterface(kp entities.KeypadDimension) ([]string, er return d.getSvgsById(ids) } -func (d *SqliteDB) RandomSvgIdxInterface(kp entities.KeypadDimension) (models.SvgIdInterface, error) { +func (d *SqliteRepository) RandomSvgIdxInterface(kp entities.KeypadDimension) (models.SvgIdInterface, error) { return d.getRandomIds(kp.TotalAttrs()) } -func (d *SqliteDB) GetSvgStringInterface(idxs models.SvgIdInterface) ([]string, error) { +func (d *SqliteRepository) GetSvgStringInterface(idxs models.SvgIdInterface) ([]string, error) { return d.getSvgsById(idxs) } -func (d *SqliteDB) getSvgsById(ids []int) ([]string, error) { +func (d *SqliteRepository) getSvgsById(ids []int) ([]string, error) { svgs := make([]string, len(ids)) for idx, id := range ids { - svg, err := d.queries.GetSvgId(d.ctx, int64(id)) + svg, err := d.Queue.Queries.GetSvgId(d.ctx, int64(id)) if err != nil { return nil, err } @@ -424,25 +363,8 @@ func (d *SqliteDB) getSvgsById(ids []int) ([]string, error) { return svgs, nil } -func (d *SqliteDB) enqueueWriteTx(queryFunc sqlcGeneric, args any) error { - select { - case <-d.ctx.Done(): - return errors.New("database is shutting down") - default: - } - - errChan := make(chan error, 1) - writeTx := WriteTx{ - Query: queryFunc, - Args: args, - ErrChan: errChan, - } - d.writeQueue <- writeTx - return <-errChan -} - -func (d *SqliteDB) getRandomIds(count int) ([]int, error) { - tx, err := d.db.Begin() +func (d *SqliteRepository) getRandomIds(count int) ([]int, error) { + tx, err := d.Queue.Db.Begin() if err != nil { log.Print(err) return nil, config.ErrSqliteTx diff --git a/internal/db/sqlite_db_test.go b/internal/repository/sqlite_repository_test.go similarity index 76% rename from internal/db/sqlite_db_test.go rename to internal/repository/sqlite_repository_test.go index 0c6d11e..6db6099 100644 --- a/internal/db/sqlite_db_test.go +++ b/internal/repository/sqlite_repository_test.go @@ -1,28 +1,31 @@ -package db +package repository import ( + "context" "github.com/stretchr/testify/assert" "go-nkode/internal/entities" "go-nkode/internal/models" + sqlite_queue "go-nkode/internal/sqlc" "os" "testing" ) func TestNewSqliteDB(t *testing.T) { - dbFile := os.Getenv("TEST_DB") + dbPath := os.Getenv("TEST_DB") // sql_driver.MakeTables(dbFile) - db, err := NewSqliteDB(dbFile) + ctx := context.Background() + sqliteDb, err := sqlite_queue.OpenSqliteDb(dbPath) assert.NoError(t, err) - defer db.Close() - testSignupLoginRenew(t, db) - testSqliteDBRandomSvgInterface(t, db) - // if _, err := os.Stat(dbFile); err == nil { - // err = os.Remove(dbFile) - // assert.NoError(t, err) - // } else { - // assert.NoError(t, err) - // } + queue, err := sqlite_queue.NewQueue(sqliteDb, ctx) + assert.NoError(t, err) + + queue.Start() + defer queue.Stop() + db := NewSqliteRepository(queue, ctx) + assert.NoError(t, err) + testSignupLoginRenew(t, &db) + testSqliteDBRandomSvgInterface(t, &db) } func testSignupLoginRenew(t *testing.T, db CustomerUserRepository) { diff --git a/internal/sqlc/sqlite_queue.go b/internal/sqlc/sqlite_queue.go new file mode 100644 index 0000000..20c17e6 --- /dev/null +++ b/internal/sqlc/sqlite_queue.go @@ -0,0 +1,93 @@ +package sqlc + +import ( + "context" + "database/sql" + "errors" + "fmt" + "sync" +) + +const writeBufferSize = 100 + +type SqlcGeneric func(*Queries, context.Context, any) error + +type WriteTx struct { + ErrChan chan error + Query SqlcGeneric + Args interface{} +} + +type Queue struct { + Queries *Queries + Db *sql.DB + WriteQueue chan WriteTx + wg sync.WaitGroup + ctx context.Context + cancel context.CancelFunc +} + +func NewQueue(sqlDb *sql.DB, ctx context.Context) (*Queue, error) { + ctx, cancel := context.WithCancel(context.Background()) + sqldb := &Queue{ + Queries: New(sqlDb), + Db: sqlDb, + WriteQueue: make(chan WriteTx, writeBufferSize), + ctx: ctx, + cancel: cancel, + } + + return sqldb, nil +} + +func (d *Queue) Start() { + d.wg.Add(1) + defer d.wg.Done() + go func() { + 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 *Queue) Stop() error { + d.cancel() + d.wg.Wait() + close(d.WriteQueue) + return d.Db.Close() +} + +func (d *Queue) EnqueueWriteTx(queryFunc SqlcGeneric, args any) error { + select { + case <-d.ctx.Done(): + return errors.New("database is shutting down") + default: + } + + errChan := make(chan error, 1) + writeTx := WriteTx{ + Query: queryFunc, + Args: args, + ErrChan: errChan, + } + d.WriteQueue <- writeTx + return <-errChan +} + +func OpenSqliteDb(dbPath string) (*sql.DB, error) { + sqliteDb, err := sql.Open("sqlite3", dbPath) + if err != nil { + return nil, fmt.Errorf("failed to open database: %w", err) + } + + if err := sqliteDb.Ping(); err != nil { + return nil, fmt.Errorf("failed to connect to database: %w", err) + } + return sqliteDb, nil +} -- 2.49.1