From 2160e087ced699b8e50655cf0937777ad7103701 Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 20 Nov 2024 16:52:38 -0600 Subject: [PATCH 01/41] add compose --- .env | 1 + compose.yaml | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 .env create mode 100644 compose.yaml diff --git a/.env b/.env new file mode 100644 index 0000000..5c01d57 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +JWT_SECRET=cab2f6a968c2a11601bb33c41c5940b7 \ No newline at end of file diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..0b3246f --- /dev/null +++ b/compose.yaml @@ -0,0 +1,16 @@ +version: '3.9' + +services: + go-nkode: + container_name: go-nkode + image: registry.donovankelly.dev/go-nkode + volumes: + - /var/go-nkode/sqlite:/app/data/sqlite + - /var/go-nkode/icons:/app/data/icons + + # .env should contain JWT_SECRET + env_file: + - .env + + ports: + - "8080:8080" \ No newline at end of file -- 2.49.1 From 80c15e9a52b952e3af6f5f873f510754f82da9e4 Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 20 Nov 2024 17:13:03 -0600 Subject: [PATCH 02/41] interperable port --- compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose.yaml b/compose.yaml index 0b3246f..ea76e3c 100644 --- a/compose.yaml +++ b/compose.yaml @@ -13,4 +13,4 @@ services: - .env ports: - - "8080:8080" \ No newline at end of file + - "{PORT}:{PORT}" \ No newline at end of file -- 2.49.1 From a0a698677a8c852eada0cf2009cc54edb436cf2f Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 20 Nov 2024 17:19:20 -0600 Subject: [PATCH 03/41] remove ports --- compose.yaml | 3 --- 1 file changed, 3 deletions(-) diff --git a/compose.yaml b/compose.yaml index ea76e3c..2531c7a 100644 --- a/compose.yaml +++ b/compose.yaml @@ -11,6 +11,3 @@ services: # .env should contain JWT_SECRET env_file: - .env - - ports: - - "{PORT}:{PORT}" \ No newline at end of file -- 2.49.1 From 23e6e54c4ee5cb32f1f387f6e8cee5c9a50baca6 Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 20 Nov 2024 17:23:31 -0600 Subject: [PATCH 04/41] remove image and add build --- compose.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compose.yaml b/compose.yaml index 2531c7a..d999993 100644 --- a/compose.yaml +++ b/compose.yaml @@ -3,7 +3,8 @@ version: '3.9' services: go-nkode: container_name: go-nkode - image: registry.donovankelly.dev/go-nkode + build: # This tells Docker Compose to build the image first + context: . volumes: - /var/go-nkode/sqlite:/app/data/sqlite - /var/go-nkode/icons:/app/data/icons -- 2.49.1 From ad42f6289c902b65d8ca5f8ef059de8a1f9a8d7e Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 20 Nov 2024 17:25:54 -0600 Subject: [PATCH 05/41] add jwt secret as ${JWT_SECRET} --- compose.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compose.yaml b/compose.yaml index d999993..2f5c3d5 100644 --- a/compose.yaml +++ b/compose.yaml @@ -10,5 +10,5 @@ services: - /var/go-nkode/icons:/app/data/icons # .env should contain JWT_SECRET - env_file: - - .env + environment: + - JWT_SECRET=${JWT_SECRET} -- 2.49.1 From 3d0d06f2cc24d7a46d3c556fc5ba480388554ed0 Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 20 Nov 2024 19:48:21 -0600 Subject: [PATCH 06/41] fix proxy error --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 944c04e..0328a66 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,6 +11,8 @@ VOLUME /app/data/icons # Copy go.mod and go.sum files COPY go.mod go.sum ./ +ENV GOPROXY=direct + # Download all dependencies RUN go mod download -- 2.49.1 From 1fa0ee9d093405197d6e6cab43bf6b23ba99477f Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 20 Nov 2024 20:05:06 -0600 Subject: [PATCH 07/41] test network --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 0328a66..025f611 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,6 +12,7 @@ VOLUME /app/data/icons COPY go.mod go.sum ./ ENV GOPROXY=direct +RUN curl -I https://github.com # Download all dependencies RUN go mod download -- 2.49.1 From 9283e638eaee1e65f48994f74ee54617490b0b91 Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 20 Nov 2024 20:15:21 -0600 Subject: [PATCH 08/41] test network --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 025f611..3956d90 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,8 +11,8 @@ VOLUME /app/data/icons # Copy go.mod and go.sum files COPY go.mod go.sum ./ -ENV GOPROXY=direct -RUN curl -I https://github.com +RUN curl -I https://proxy.golang.org +RUN curl -I https://sum.golang.org # Download all dependencies RUN go mod download -- 2.49.1 From 0024a58eb49c133b6f96a4e84a2f92e9a3202ad6 Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 20 Nov 2024 20:16:38 -0600 Subject: [PATCH 09/41] test network --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3956d90..8c6897a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,8 +11,7 @@ VOLUME /app/data/icons # Copy go.mod and go.sum files COPY go.mod go.sum ./ -RUN curl -I https://proxy.golang.org -RUN curl -I https://sum.golang.org +RUN curl -I https://proxy.golang.org/github.com/aws/aws-sdk-go/@v/v1.55.5.zip # Download all dependencies RUN go mod download -- 2.49.1 From ba9be11f934af30bc2fa807287292f5ae8f45074 Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 20 Nov 2024 20:25:50 -0600 Subject: [PATCH 10/41] trying different go version --- Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8c6897a..0f25a4b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Stage 1: Build -FROM golang:1.23 AS builder +FROM golang:1.21 AS builder # Set the working directory inside the container WORKDIR /app @@ -11,7 +11,6 @@ VOLUME /app/data/icons # Copy go.mod and go.sum files COPY go.mod go.sum ./ -RUN curl -I https://proxy.golang.org/github.com/aws/aws-sdk-go/@v/v1.55.5.zip # Download all dependencies RUN go mod download -- 2.49.1 From 3fe7c47ed18e4a8200064bbe6e5cceace9d00fca Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 20 Nov 2024 20:26:44 -0600 Subject: [PATCH 11/41] trying different go version --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0f25a4b..e17e04b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Stage 1: Build -FROM golang:1.21 AS builder +FROM golang:1.22 AS builder # Set the working directory inside the container WORKDIR /app -- 2.49.1 From e12225175166962b3df74827229126dcbc539026 Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 20 Nov 2024 20:29:16 -0600 Subject: [PATCH 12/41] remove module that gives error --- go.mod | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.mod b/go.mod index 1c77d0c..22bbd66 100644 --- a/go.mod +++ b/go.mod @@ -9,11 +9,9 @@ require ( github.com/mattn/go-sqlite3 v1.14.22 github.com/stretchr/testify v1.9.0 golang.org/x/crypto v0.26.0 - golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 ) require ( - github.com/aws/aws-sdk-go v1.55.5 // indirect github.com/aws/aws-sdk-go-v2 v1.31.0 // indirect github.com/aws/aws-sdk-go-v2/config v1.27.37 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.35 // indirect -- 2.49.1 From c2b6533c6e084559b4ed1f0a081e3724116bc96e Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 20 Nov 2024 20:29:21 -0600 Subject: [PATCH 13/41] remove module that gives error --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e17e04b..9cb3b79 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Stage 1: Build -FROM golang:1.22 AS builder +FROM golang:1.23 AS builder # Set the working directory inside the container WORKDIR /app -- 2.49.1 From 6542227cc536669badcb745f3bb03412d3d6cc13 Mon Sep 17 00:00:00 2001 From: Donovan Date: Sun, 24 Nov 2024 10:15:03 -0600 Subject: [PATCH 14/41] add icons folder to ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 319175e..a9a8fd3 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ secrets.json flaticon_svgs flaticon_colored_svgs +icons -- 2.49.1 From 2a038661801e89b92043c7be5b721ea8cd0a2888 Mon Sep 17 00:00:00 2001 From: Donovan Date: Mon, 25 Nov 2024 10:11:24 -0600 Subject: [PATCH 15/41] remame coolify_compose.yaml remove old scripts --- .env | 1 - Dockerfile | 8 ++++---- compose.yaml => coolify_compose.yaml | 10 +++++----- deploy_api.sh | 25 ------------------------- docker_build.sh | 2 +- docker_run.sh | 5 ----- secure_bytes.sh | 14 -------------- 7 files changed, 10 insertions(+), 55 deletions(-) delete mode 100644 .env rename compose.yaml => coolify_compose.yaml (54%) delete mode 100644 deploy_api.sh delete mode 100644 docker_run.sh delete mode 100644 secure_bytes.sh diff --git a/.env b/.env deleted file mode 100644 index 5c01d57..0000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -JWT_SECRET=cab2f6a968c2a11601bb33c41c5940b7 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 9cb3b79..9a700c6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -25,10 +25,10 @@ RUN go build FROM debian:bookworm-slim #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 +#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 diff --git a/compose.yaml b/coolify_compose.yaml similarity index 54% rename from compose.yaml rename to coolify_compose.yaml index 2f5c3d5..53ee3fb 100644 --- a/compose.yaml +++ b/coolify_compose.yaml @@ -1,14 +1,14 @@ -version: '3.9' - services: go-nkode: container_name: go-nkode - build: # This tells Docker Compose to build the image first - context: . + image: registry.infra.nkode.tech/go-nkode volumes: - /var/go-nkode/sqlite:/app/data/sqlite - /var/go-nkode/icons:/app/data/icons - # .env should contain JWT_SECRET environment: - JWT_SECRET=${JWT_SECRET} + - FRONTEND_HOST=${FRONTEND_HOST} + - SVG_DIR=${SVG_DIR} + - DB_PATH=${DB_PATH} + - SQLITE_DB=${SQLITE_DB} diff --git a/deploy_api.sh b/deploy_api.sh deleted file mode 100644 index f960970..0000000 --- a/deploy_api.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -# Create a temporary directory to hold the files -#mkdir -p /tmp/nkodeapi -# -#cp -r ./core/* /tmp/nkodeapi/ -#cp -r ./hashset/* /tmp/nkodeapi/ -#cp -r ./py-builtin/* /tmp/nkodeapi/ -#cp -r ./util/* /tmp/nkodeapi/ -# -#cp go.mod /tmp/nkodeapi/ -#cp main.go /tmp/nkodeapi/ - - -# Disable extended attributes and create the tar file -export COPYFILE_DISABLE=1 -tar -cvf go-nkode.tar -C ../ go-nkode - - -#scp go-nkode.tar dkelly@api.nkode.tech:/home/dkelly - -scp api.nkode.tech dkelly@217.21.78.137:/home/dkelly -scp go-nkode.tar dkelly@217.21.78.137:/home/dkelly - -rm go-nkode.tar diff --git a/docker_build.sh b/docker_build.sh index 55acca3..26b89f7 100644 --- a/docker_build.sh +++ b/docker_build.sh @@ -1 +1 @@ -docker build -t go-nkode . \ No newline at end of file +docker buildx build --platform linux/amd64,linux/arm64 -t registry.infra.nkode.tech/go-nkode:latest --push . diff --git a/docker_run.sh b/docker_run.sh deleted file mode 100644 index 236e43e..0000000 --- a/docker_run.sh +++ /dev/null @@ -1,5 +0,0 @@ -docker run --name go-nkode -p 8080:8080 \ - -v /Users/donov/databases/:/app/data/sqlite \ - -v /Users/donov/Desktop/go-nkode/core/sqlite-init/flaticon_colored_svgs/:/app/data/icons \ - -e JWT_SECRET=cab2f6a968c2a11601bb33c41c5940b7 \ - go-nkode \ No newline at end of file diff --git a/secure_bytes.sh b/secure_bytes.sh deleted file mode 100644 index d9f7692..0000000 --- a/secure_bytes.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -# Define the number of bytes you want to generate -num_bytes=16 - -# Use dd to read cryptographically secure bytes from /dev/urandom -# and convert them to hexadecimal using od -secure_bytes=$(dd if=/dev/urandom bs=1 count=$num_bytes 2>/dev/null | od -An -tx1) - -# Remove leading/trailing spaces and concatenate the hex bytes into a single string -secure_bytes=$(echo $secure_bytes | tr -d ' \n') - -# Output the result as a hexadecimal string -echo "Cryptographically secure bytes (as hex): $secure_bytes" -- 2.49.1 From e1df64e708dbe8cd54af99f3c505f79eb918e7b5 Mon Sep 17 00:00:00 2001 From: Donovan Date: Mon, 25 Nov 2024 10:11:58 -0600 Subject: [PATCH 16/41] move scripts to folder --- backup_sqlite.sh => script/backup_sqlite.sh | 0 docker_build.sh => script/docker_build.sh | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename backup_sqlite.sh => script/backup_sqlite.sh (100%) rename docker_build.sh => script/docker_build.sh (100%) diff --git a/backup_sqlite.sh b/script/backup_sqlite.sh similarity index 100% rename from backup_sqlite.sh rename to script/backup_sqlite.sh diff --git a/docker_build.sh b/script/docker_build.sh similarity index 100% rename from docker_build.sh rename to script/docker_build.sh -- 2.49.1 From aef6f000a1beca2aadae8933e4bcbffc40dc1247 Mon Sep 17 00:00:00 2001 From: Donovan Date: Mon, 25 Nov 2024 10:12:32 -0600 Subject: [PATCH 17/41] remove nginx --- api.nkode.tech | 38 -------------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 api.nkode.tech diff --git a/api.nkode.tech b/api.nkode.tech deleted file mode 100644 index 8d3af1c..0000000 --- a/api.nkode.tech +++ /dev/null @@ -1,38 +0,0 @@ -server { - listen 443 ssl http2; - server_name api.nkode.tech; - - ssl_certificate /etc/letsencrypt/live/api.nkode.tech/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/api.nkode.tech/privkey.pem; - ssl_protocols TLSv1.2 TLSv1.3; - ssl_prefer_server_ciphers on; - ssl_dhparam /etc/ssl/certs/dhparam.pem; - ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; - ssl_session_cache shared:SSL:10m; - ssl_session_timeout 10m; - ssl_stapling on; - ssl_stapling_verify on; - resolver 8.8.8.8 8.8.4.4 valid=300s; - resolver_timeout 5s; - - add_header X-Content-Type-Options nosniff; - add_header X-Frame-Options DENY; - add_header X-XSS-Protection "1; mode=block"; - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; - - location / { - proxy_pass http://127.0.0.1:8080; # Your application port - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - } -} - -server { - listen 80; - server_name api.nkode.tech; - - # Redirect all HTTP traffic to HTTPS - return 301 https://$host:443$request_uri; -} -- 2.49.1 From f18b88934e64b11ae4faded9fc82dd56d299a6de Mon Sep 17 00:00:00 2001 From: Donovan Date: Mon, 25 Nov 2024 10:12:42 -0600 Subject: [PATCH 18/41] move compose to its own folder --- coolify_compose.yaml => compose/coolify_compose.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename coolify_compose.yaml => compose/coolify_compose.yaml (100%) diff --git a/coolify_compose.yaml b/compose/coolify_compose.yaml similarity index 100% rename from coolify_compose.yaml rename to compose/coolify_compose.yaml -- 2.49.1 From f6e8fde567dd080983c79c4a1723201cb3a205eb Mon Sep 17 00:00:00 2001 From: Donovan Date: Mon, 25 Nov 2024 13:06:55 -0600 Subject: [PATCH 19/41] add local compose --- Dockerfile | 6 +++--- compose/coolify_compose.yaml | 5 +---- compose/local_compose.yaml | 13 +++++++++++++ 3 files changed, 17 insertions(+), 7 deletions(-) create mode 100644 compose/local_compose.yaml diff --git a/Dockerfile b/Dockerfile index 9a700c6..18ccabd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,9 +26,9 @@ FROM debian:bookworm-slim #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 +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 diff --git a/compose/coolify_compose.yaml b/compose/coolify_compose.yaml index 53ee3fb..2112488 100644 --- a/compose/coolify_compose.yaml +++ b/compose/coolify_compose.yaml @@ -8,7 +8,4 @@ services: environment: - JWT_SECRET=${JWT_SECRET} - - FRONTEND_HOST=${FRONTEND_HOST} - - SVG_DIR=${SVG_DIR} - - DB_PATH=${DB_PATH} - - SQLITE_DB=${SQLITE_DB} + - FRONTEND_HOST=https://app.nkode.tech diff --git a/compose/local_compose.yaml b/compose/local_compose.yaml new file mode 100644 index 0000000..e7049d2 --- /dev/null +++ b/compose/local_compose.yaml @@ -0,0 +1,13 @@ +services: + go-nkode: + container_name: go-nkode + image: registry.infra.nkode.tech/go-nkode + volumes: + - /var/go-nkode/sqlite:/app/data/sqlite + - /var/go-nkode/icons:/app/data/icons + + environment: + - JWT_SECRET=0123456789 + - FRONTEND_HOST=http://localhost:8090 + ports: + - "8070:8080" \ No newline at end of file -- 2.49.1 From 052f95702ddde3ba4d9a1566fb83a0b9655bb9b8 Mon Sep 17 00:00:00 2001 From: Donovan Date: Tue, 26 Nov 2024 11:31:46 -0600 Subject: [PATCH 20/41] idiomatic project structure --- cmd/main.go | 108 +++++++++++++ main_test.go => cmd/main_test.go | 79 +++++----- {core => config}/config.go | 2 +- {core => config}/constants.go | 21 +-- go.mod | 47 +++++- go.sum | 144 +++++++++++++++++- internal/api/db_interface.go | 20 +++ .../api/handler.go | 72 +++++---- {core => internal/api}/nkode_api.go | 82 +++++----- {core => internal/api}/nkode_api_test.go | 47 +++--- {core => internal/db}/in_memory_db.go | 43 +++--- .../db}/sqlite-init/json/academicons.json | 0 .../db}/sqlite-init/json/akar-icons.json | 0 .../db}/sqlite-init/json/ant-design.json | 0 .../db}/sqlite-init/json/arcticons.json | 0 .../db}/sqlite-init/json/basil.json | 0 .../db}/sqlite-init/json/bitcoin-icons.json | 0 .../db}/sqlite-init/sqlite_init.go | 0 {core => internal/db}/sqlite_db.go | 114 +++++++------- {core => internal/db}/sqlite_db_test.go | 24 +-- .../email_queue.go => internal/email/queue.go | 5 +- .../email/queue_test.go | 2 +- {core => internal/models}/customer.go | 23 +-- .../models}/customer_attributes.go | 24 +-- {core => internal/models}/customer_test.go | 2 +- {core => internal/models}/keypad_dimension.go | 7 +- core/type.go => internal/models/models.go | 33 ++-- .../models/policy.go | 6 +- {core => internal/models}/test_helper.go | 6 +- {core => internal/models}/user.go | 19 +-- {core => internal/models}/user_cipher_keys.go | 33 ++-- {core => internal/models}/user_interface.go | 59 +++---- .../models}/user_signup_session.go | 44 +++--- {core => internal/models}/user_test.go | 4 +- {core => internal/security}/jwt_claims.go | 29 ++-- .../security}/jwt_claims_test.go | 6 +- {util => internal/security}/util.go | 8 +- {util => internal/security}/util_test.go | 2 +- {hashset => internal/utils}/hashset.go | 2 +- {hashset => internal/utils}/hashset_test.go | 2 +- {py-builtin => internal/utils}/py-builtin.go | 2 +- main.go | 77 ---------- {script => scripts}/backup_sqlite.sh | 0 {script => scripts}/docker_build.sh | 0 44 files changed, 717 insertions(+), 481 deletions(-) create mode 100644 cmd/main.go rename main_test.go => cmd/main_test.go (54%) rename {core => config}/config.go (76%) rename {core => config}/constants.go (88%) create mode 100644 internal/api/db_interface.go rename core/nkode_handler.go => internal/api/handler.go (79%) rename {core => internal/api}/nkode_api.go (62%) rename {core => internal/api}/nkode_api_test.go (60%) rename {core => internal/db}/in_memory_db.go (60%) rename {core => internal/db}/sqlite-init/json/academicons.json (100%) rename {core => internal/db}/sqlite-init/json/akar-icons.json (100%) rename {core => internal/db}/sqlite-init/json/ant-design.json (100%) rename {core => internal/db}/sqlite-init/json/arcticons.json (100%) rename {core => internal/db}/sqlite-init/json/basil.json (100%) rename {core => internal/db}/sqlite-init/json/bitcoin-icons.json (100%) rename {core => internal/db}/sqlite-init/sqlite_init.go (100%) rename {core => internal/db}/sqlite_db.go (69%) rename {core => internal/db}/sqlite_db_test.go (62%) rename core/email_queue.go => internal/email/queue.go (98%) rename core/email_queue_test.go => internal/email/queue_test.go (97%) rename {core => internal/models}/customer.go (77%) rename {core => internal/models}/customer_attributes.go (72%) rename {core => internal/models}/customer_test.go (99%) rename {core => internal/models}/keypad_dimension.go (91%) rename core/type.go => internal/models/models.go (84%) rename core/nkode_policy.go => internal/models/policy.go (90%) rename {core => internal/models}/test_helper.go (81%) rename {core => internal/models}/user.go (88%) rename {core => internal/models}/user_cipher_keys.go (84%) rename {core => internal/models}/user_interface.go (65%) rename {core => internal/models}/user_signup_session.go (82%) rename {core => internal/models}/user_test.go (98%) rename {core => internal/security}/jwt_claims.go (79%) rename {core => internal/security}/jwt_claims_test.go (87%) rename {util => internal/security}/util.go (98%) rename {util => internal/security}/util_test.go (98%) rename {hashset => internal/utils}/hashset.go (98%) rename {hashset => internal/utils}/hashset_test.go (97%) rename {py-builtin => internal/utils}/py-builtin.go (88%) delete mode 100644 main.go rename {script => scripts}/backup_sqlite.sh (100%) rename {script => scripts}/docker_build.sh (100%) diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..d04297d --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,108 @@ +package main + +import ( + "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" + "log" + "net/http" + "os" +) + +const ( + emailQueueBufferSize = 100 + maxEmailsPerSecond = 13 // SES allows 14, but I don't want to push it +) + +// @title NKode API +// @version 1.0 +// @description This is the NKode API server. +// @termsOfService http://nkode.example.com/terms/ + +// @contact.name API Support +// @contact.url http://nkode.example.com/support +// @contact.email support@nkode.example.com + +// @license.name MIT +// @license.url https://opensource.org/licenses/MIT + +// @host localhost:8080 +// @BasePath / + +// @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") + } + db := db.NewSqliteDB(dbPath) + defer db.CloseDb() + + sesClient := email.NewSESClient() + emailQueue := email.NewEmailQueue(emailQueueBufferSize, maxEmailsPerSecond, &sesClient) + emailQueue.Start() + defer emailQueue.Stop() + + nkodeApi := api.NewNKodeAPI(db, emailQueue) + AddDefaultCustomer(nkodeApi) + handler := api.NKodeHandler{Api: nkodeApi} + + mux := http.NewServeMux() + mux.Handle(api.CreateNewCustomer, &handler) + mux.Handle(api.GenerateSignupResetInterface, &handler) + mux.Handle(api.SetNKode, &handler) + mux.Handle(api.ConfirmNKode, &handler) + mux.Handle(api.GetLoginInterface, &handler) + mux.Handle(api.Login, &handler) + mux.Handle(api.RenewAttributes, &handler) + mux.Handle(api.RandomSvgInterface, &handler) + mux.Handle(api.RefreshToken, &handler) + mux.Handle(api.ResetNKode, &handler) + + // Serve Swagger UI + mux.Handle("/swagger/", httpSwagger.WrapHandler) + + fmt.Println("Running on localhost:8080...") + log.Fatal(http.ListenAndServe(":8080", corsMiddleware(mux))) +} + +func corsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Set the CORS headers + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE") + w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") + + // Handle preflight requests + if r.Method == http.MethodOptions { + w.WriteHeader(http.StatusNoContent) + return + } + + // Call the next handler + next.ServeHTTP(w, r) + }) +} + +func AddDefaultCustomer(nkodeApi api.NKodeAPI) { + newId, err := uuid.Parse("ed9ed6e0-082c-4b57-8d8c-f00ed6493457") + if err != nil { + log.Fatal(err) + } + customerId := models.CustomerId(newId) + nkodePolicy := models.NewDefaultNKodePolicy() + _, err = nkodeApi.CreateNewCustomer(nkodePolicy, &customerId) + if err != nil { + log.Println(err) + } else { + log.Println("created new customer: ", newId) + } +} diff --git a/main_test.go b/cmd/main_test.go similarity index 54% rename from main_test.go rename to cmd/main_test.go index 0ab8851..0834b66 100644 --- a/main_test.go +++ b/cmd/main_test.go @@ -5,8 +5,9 @@ import ( "encoding/json" "fmt" "github.com/stretchr/testify/assert" - "go-nkode/core" - "go-nkode/util" + "go-nkode/internal/api" + "go-nkode/internal/models" + "go-nkode/internal/security" "io" "net/http" "strings" @@ -15,94 +16,94 @@ import ( func TestApi(t *testing.T) { base := "http://localhost:8080" - newCustomerBody := core.NewCustomerPost{ - NKodePolicy: core.NewDefaultNKodePolicy(), + newCustomerBody := models.NewCustomerPost{ + NKodePolicy: models.NewDefaultNKodePolicy(), } - kp := core.KeypadDimension{ + kp := models.KeypadDimension{ AttrsPerKey: 14, NumbOfKeys: 10, } - var customerResp core.CreateNewCustomerResp - testApiPost(t, base+core.CreateNewCustomer, newCustomerBody, &customerResp) + var customerResp models.CreateNewCustomerResp + testApiPost(t, base+api.CreateNewCustomer, newCustomerBody, &customerResp) - userEmail := "test_username" + util.GenerateRandomString(12) + "@example.com" - signupInterfaceBody := core.GenerateSignupRestInterfacePost{ + userEmail := "test_username" + security.GenerateRandomString(12) + "@example.com" + signupInterfaceBody := models.GenerateSignupRestInterfacePost{ CustomerId: customerResp.CustomerId, AttrsPerKey: kp.AttrsPerKey, NumbOfKeys: kp.NumbOfKeys, UserEmail: strings.ToUpper(userEmail), // should be case-insensitive Reset: false, } - var signupInterfaceResp core.GenerateSignupResetInterfaceResp - testApiPost(t, base+core.GenerateSignupResetInterface, signupInterfaceBody, &signupInterfaceResp) + var signupInterfaceResp models.GenerateSignupResetInterfaceResp + testApiPost(t, base+api.GenerateSignupResetInterface, signupInterfaceBody, &signupInterfaceResp) assert.Len(t, signupInterfaceResp.SvgInterface, kp.TotalAttrs()) passcodeLen := 4 setInterface := signupInterfaceResp.UserIdxInterface userPasscode := setInterface[:passcodeLen] - kpSet := core.KeypadDimension{NumbOfKeys: kp.NumbOfKeys, AttrsPerKey: kp.NumbOfKeys} - setKeySelection, err := core.SelectKeyByAttrIdx(setInterface, userPasscode, kpSet) + kpSet := models.KeypadDimension{NumbOfKeys: kp.NumbOfKeys, AttrsPerKey: kp.NumbOfKeys} + setKeySelection, err := models.SelectKeyByAttrIdx(setInterface, userPasscode, kpSet) assert.NoError(t, err) - setNKodeBody := core.SetNKodePost{ + setNKodeBody := models.SetNKodePost{ CustomerId: customerResp.CustomerId, SessionId: signupInterfaceResp.SessionId, KeySelection: setKeySelection, } - var setNKodeResp core.SetNKodeResp - testApiPost(t, base+core.SetNKode, setNKodeBody, &setNKodeResp) + var setNKodeResp models.SetNKodeResp + testApiPost(t, base+api.SetNKode, setNKodeBody, &setNKodeResp) confirmInterface := setNKodeResp.UserInterface - confirmKeySelection, err := core.SelectKeyByAttrIdx(confirmInterface, userPasscode, kpSet) + confirmKeySelection, err := models.SelectKeyByAttrIdx(confirmInterface, userPasscode, kpSet) assert.NoError(t, err) - confirmNKodeBody := core.ConfirmNKodePost{ + confirmNKodeBody := models.ConfirmNKodePost{ CustomerId: customerResp.CustomerId, KeySelection: confirmKeySelection, SessionId: signupInterfaceResp.SessionId, } - testApiPost(t, base+core.ConfirmNKode, confirmNKodeBody, nil) + testApiPost(t, base+api.ConfirmNKode, confirmNKodeBody, nil) - loginInterfaceBody := core.GetLoginInterfacePost{ + loginInterfaceBody := models.GetLoginInterfacePost{ CustomerId: customerResp.CustomerId, UserEmail: userEmail, } - var loginInterfaceResp core.GetLoginInterfaceResp - testApiPost(t, base+core.GetLoginInterface, loginInterfaceBody, &loginInterfaceResp) + var loginInterfaceResp models.GetLoginInterfaceResp + testApiPost(t, base+api.GetLoginInterface, loginInterfaceBody, &loginInterfaceResp) assert.Equal(t, loginInterfaceResp.AttrsPerKey, kp.AttrsPerKey) assert.Equal(t, loginInterfaceResp.NumbOfKeys, kp.NumbOfKeys) - loginKeySelection, err := core.SelectKeyByAttrIdx(loginInterfaceResp.UserIdxInterface, userPasscode, kp) + loginKeySelection, err := models.SelectKeyByAttrIdx(loginInterfaceResp.UserIdxInterface, userPasscode, kp) assert.NoError(t, err) - loginBody := core.LoginPost{ + loginBody := models.LoginPost{ CustomerId: customerResp.CustomerId, UserEmail: userEmail, KeySelection: loginKeySelection, } - var jwtTokens core.AuthenticationTokens - testApiPost(t, base+core.Login, loginBody, &jwtTokens) - refreshClaims, err := core.ParseRegisteredClaimToken(jwtTokens.RefreshToken) + var jwtTokens security.AuthenticationTokens + testApiPost(t, base+api.Login, loginBody, &jwtTokens) + refreshClaims, err := security.ParseRegisteredClaimToken(jwtTokens.RefreshToken) assert.Equal(t, refreshClaims.Subject, userEmail) - accessClaims, err := core.ParseRegisteredClaimToken(jwtTokens.AccessToken) + accessClaims, err := security.ParseRegisteredClaimToken(jwtTokens.AccessToken) assert.Equal(t, accessClaims.Subject, userEmail) - renewBody := core.RenewAttributesPost{CustomerId: customerResp.CustomerId} - testApiPost(t, base+core.RenewAttributes, renewBody, nil) + renewBody := models.RenewAttributesPost{CustomerId: customerResp.CustomerId} + testApiPost(t, base+api.RenewAttributes, renewBody, nil) - loginKeySelection, err = core.SelectKeyByAttrIdx(loginInterfaceResp.UserIdxInterface, userPasscode, kp) + loginKeySelection, err = models.SelectKeyByAttrIdx(loginInterfaceResp.UserIdxInterface, userPasscode, kp) assert.NoError(t, err) - loginBody = core.LoginPost{ + loginBody = models.LoginPost{ CustomerId: customerResp.CustomerId, UserEmail: userEmail, KeySelection: loginKeySelection, } - testApiPost(t, base+core.Login, loginBody, &jwtTokens) + testApiPost(t, base+api.Login, loginBody, &jwtTokens) - var randomSvgInterfaceResp core.RandomSvgInterfaceResp - testApiGet(t, base+core.RandomSvgInterface, &randomSvgInterfaceResp, "") - assert.Equal(t, core.KeypadMax.TotalAttrs(), len(randomSvgInterfaceResp.Svgs)) + var randomSvgInterfaceResp models.RandomSvgInterfaceResp + testApiGet(t, base+api.RandomSvgInterface, &randomSvgInterfaceResp, "") + assert.Equal(t, models.KeypadMax.TotalAttrs(), len(randomSvgInterfaceResp.Svgs)) - var refreshTokenResp core.RefreshTokenResp + var refreshTokenResp models.RefreshTokenResp - testApiGet(t, base+core.RefreshToken, &refreshTokenResp, jwtTokens.RefreshToken) - accessClaims, err = core.ParseRegisteredClaimToken(refreshTokenResp.AccessToken) + testApiGet(t, base+api.RefreshToken, &refreshTokenResp, jwtTokens.RefreshToken) + accessClaims, err = security.ParseRegisteredClaimToken(refreshTokenResp.AccessToken) assert.NoError(t, err) assert.Equal(t, accessClaims.Subject, userEmail) } diff --git a/core/config.go b/config/config.go similarity index 76% rename from core/config.go rename to config/config.go index 0728bd1..78dba4f 100644 --- a/core/config.go +++ b/config/config.go @@ -1,4 +1,4 @@ -package core +package config const ( FrontendHost = "https://nkode.tech" diff --git a/core/constants.go b/config/constants.go similarity index 88% rename from core/constants.go rename to config/constants.go index bf65512..54d18ac 100644 --- a/core/constants.go +++ b/config/constants.go @@ -1,4 +1,4 @@ -package core +package config import ( "errors" @@ -64,22 +64,3 @@ var HttpErrMap = map[error]int{ ErrInvalidNKode: http.StatusBadRequest, ErrStringIsNotAnSVG: http.StatusInternalServerError, } - -var SetColors = []RGBColor{ - {0, 0, 0}, // Black - {255, 0, 0}, // Red - {0, 128, 0}, // Dark Green - {0, 0, 255}, // Blue - {244, 200, 60}, // Yellow - {255, 0, 255}, // Magenta - {0, 200, 200}, // Cyan - {127, 0, 127}, // Purple - {232, 92, 13}, // Orange - {0, 127, 127}, // Teal - {127, 127, 0}, // Olive - {127, 0, 0}, // Dark Red - {128, 128, 128}, // Gray - {228, 102, 102}, // Dark Purple - {185, 17, 240}, // Salmon - {16, 200, 100}, // Green -} diff --git a/go.mod b/go.mod index 22bbd66..1b11cd1 100644 --- a/go.mod +++ b/go.mod @@ -5,15 +5,22 @@ go 1.22.0 toolchain go1.23.0 require ( + github.com/aws/aws-sdk-go-v2 v1.31.0 + github.com/aws/aws-sdk-go-v2/config v1.27.37 + github.com/aws/aws-sdk-go-v2/service/ses v1.27.1 + github.com/golang-jwt/jwt/v5 v5.2.1 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 - golang.org/x/crypto v0.26.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 + golang.org/x/crypto v0.29.0 ) require ( - github.com/aws/aws-sdk-go-v2 v1.31.0 // indirect - github.com/aws/aws-sdk-go-v2/config v1.27.37 // 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 github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.18 // indirect @@ -21,15 +28,43 @@ require ( github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.5 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.20 // indirect - github.com/aws/aws-sdk-go-v2/service/ses v1.27.1 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.23.1 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.27.1 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.31.1 // indirect github.com/aws/smithy-go v1.21.0 // indirect + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/gin-gonic/gin v1.9.1 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/patrickmn/go-cache v2.1.0+incompatible // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/swaggo/files v1.0.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/net v0.31.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/text v0.20.0 // indirect + golang.org/x/tools v0.27.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 2d0c68a..b061348 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= -github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +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= github.com/aws/aws-sdk-go-v2 v1.31.0/go.mod h1:ztolYtaEUtdpf9Wftr31CJfLVjOnD/CVRkKOOYgF8hA= github.com/aws/aws-sdk-go-v2/config v1.27.37 h1:xaoIwzHVuRWRHFI0jhgEdEGc8xE1l91KaeRDsWEIncU= @@ -28,31 +28,161 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.31.1 h1:8K0UNOkZiK9Uh3HIF6Bx0rcNCftq github.com/aws/aws-sdk-go-v2/service/sts v1.31.1/go.mod h1:yMWe0F+XG0DkRZK5ODZhG7BEFYhLXi2dqGsv6tX0cgI= github.com/aws/smithy-go v1.21.0 h1:H7L8dtDRk0P1Qm6y0ji7MCYMQObJ5R9CRpyPhRUkLYA= github.com/aws/smithy-go v1.21.0/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +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= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= -golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +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= +github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ= +github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= +github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= +github.com/swaggo/swag/example/celler v0.0.0-20241025062444-99698582709d h1:zyYK35EKMhtoXGSxZJm9yxO9KzYEr0M9/63FhaBKr4c= +github.com/swaggo/swag/example/celler v0.0.0-20241025062444-99698582709d/go.mod h1:kw/fmH4DXH7Dp7d8aLEU1ub7UA82GhJJ0ZABDxEJaM0= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= +golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/api/db_interface.go b/internal/api/db_interface.go new file mode 100644 index 0000000..945b5a4 --- /dev/null +++ b/internal/api/db_interface.go @@ -0,0 +1,20 @@ +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) +} diff --git a/core/nkode_handler.go b/internal/api/handler.go similarity index 79% rename from core/nkode_handler.go rename to internal/api/handler.go index 7584514..cbcea0d 100644 --- a/core/nkode_handler.go +++ b/internal/api/handler.go @@ -1,9 +1,12 @@ -package core +package api import ( "encoding/json" "errors" "github.com/google/uuid" + "go-nkode/config" + "go-nkode/internal/models" + "go-nkode/internal/security" "log" "net/http" "strings" @@ -63,13 +66,22 @@ func (h *NKodeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } +// CreateNewCustomerHandler handles the creation of a new customer. +// @Summary Create a new customer +// @Description Creates a new customer based on the provided policy information. +// @Tags customers +// @Accept json +// @Produce json +// @Param NewCustomerPost body NewCustomerPost true "Customer creation data" +// @Success 200 {object} CreateNewCustomerResp +// @Router /create-new-customer [post] func (h *NKodeHandler) CreateNewCustomerHandler(w http.ResponseWriter, r *http.Request) { log.Print("create new customer") if r.Method != http.MethodPost { methodNotAllowed(w) return } - var customerPost NewCustomerPost + var customerPost models.NewCustomerPost if err := decodeJson(w, r, &customerPost); err != nil { return } @@ -78,7 +90,7 @@ func (h *NKodeHandler) CreateNewCustomerHandler(w http.ResponseWriter, r *http.R handleError(w, err) return } - respBody := CreateNewCustomerResp{ + respBody := models.CreateNewCustomerResp{ CustomerId: uuid.UUID(*customerId).String(), } marshalAndWriteBytes(w, respBody) @@ -91,12 +103,12 @@ func (h *NKodeHandler) GenerateSignupResetInterfaceHandler(w http.ResponseWriter return } - var signupResetPost GenerateSignupRestInterfacePost + var signupResetPost models.GenerateSignupRestInterfacePost if err := decodeJson(w, r, &signupResetPost); err != nil { return } - kp := KeypadDimension{ + kp := models.KeypadDimension{ AttrsPerKey: signupResetPost.AttrsPerKey, NumbOfKeys: signupResetPost.NumbOfKeys, } @@ -109,12 +121,12 @@ func (h *NKodeHandler) GenerateSignupResetInterfaceHandler(w http.ResponseWriter badRequest(w, malformedCustomerId) return } - userEmail, err := ParseEmail(signupResetPost.UserEmail) + userEmail, err := models.ParseEmail(signupResetPost.UserEmail) if err != nil { badRequest(w, malformedUserEmail) return } - resp, err := h.Api.GenerateSignupResetInterface(userEmail, CustomerId(customerId), kp, signupResetPost.Reset) + resp, err := h.Api.GenerateSignupResetInterface(userEmail, models.CustomerId(customerId), kp, signupResetPost.Reset) if err != nil { handleError(w, err) return @@ -129,7 +141,7 @@ func (h *NKodeHandler) SetNKodeHandler(w http.ResponseWriter, r *http.Request) { methodNotAllowed(w) return } - var setNKodePost SetNKodePost + var setNKodePost models.SetNKodePost if err := decodeJson(w, r, &setNKodePost); err != nil { return } @@ -143,12 +155,12 @@ func (h *NKodeHandler) SetNKodeHandler(w http.ResponseWriter, r *http.Request) { badRequest(w, malformedSessionId) return } - confirmInterface, err := h.Api.SetNKode(CustomerId(customerId), SessionId(sessionId), setNKodePost.KeySelection) + confirmInterface, err := h.Api.SetNKode(models.CustomerId(customerId), models.SessionId(sessionId), setNKodePost.KeySelection) if err != nil { handleError(w, err) return } - respBody := SetNKodeResp{UserInterface: confirmInterface} + respBody := models.SetNKodeResp{UserInterface: confirmInterface} marshalAndWriteBytes(w, respBody) } @@ -159,7 +171,7 @@ func (h *NKodeHandler) ConfirmNKodeHandler(w http.ResponseWriter, r *http.Reques return } - var confirmNKodePost ConfirmNKodePost + var confirmNKodePost models.ConfirmNKodePost if err := decodeJson(w, r, &confirmNKodePost); err != nil { return } @@ -173,7 +185,7 @@ func (h *NKodeHandler) ConfirmNKodeHandler(w http.ResponseWriter, r *http.Reques badRequest(w, malformedSessionId) return } - if err = h.Api.ConfirmNKode(CustomerId(customerId), SessionId(sessionId), confirmNKodePost.KeySelection); err != nil { + if err = h.Api.ConfirmNKode(models.CustomerId(customerId), models.SessionId(sessionId), confirmNKodePost.KeySelection); err != nil { handleError(w, err) return } @@ -186,7 +198,7 @@ func (h *NKodeHandler) GetLoginInterfaceHandler(w http.ResponseWriter, r *http.R methodNotAllowed(w) return } - var loginInterfacePost GetLoginInterfacePost + var loginInterfacePost models.GetLoginInterfacePost if err := decodeJson(w, r, &loginInterfacePost); err != nil { return } @@ -195,11 +207,11 @@ func (h *NKodeHandler) GetLoginInterfaceHandler(w http.ResponseWriter, r *http.R badRequest(w, malformedCustomerId) return } - userEmail, err := ParseEmail(loginInterfacePost.UserEmail) + userEmail, err := models.ParseEmail(loginInterfacePost.UserEmail) if err != nil { badRequest(w, malformedUserEmail) } - loginInterface, err := h.Api.GetLoginInterface(userEmail, CustomerId(customerId)) + loginInterface, err := h.Api.GetLoginInterface(userEmail, models.CustomerId(customerId)) if err != nil { handleError(w, err) return @@ -214,7 +226,7 @@ func (h *NKodeHandler) LoginHandler(w http.ResponseWriter, r *http.Request) { methodNotAllowed(w) return } - var loginPost LoginPost + var loginPost models.LoginPost if err := decodeJson(w, r, &loginPost); err != nil { return } @@ -223,12 +235,12 @@ func (h *NKodeHandler) LoginHandler(w http.ResponseWriter, r *http.Request) { badRequest(w, malformedCustomerId) return } - userEmail, err := ParseEmail(loginPost.UserEmail) + userEmail, err := models.ParseEmail(loginPost.UserEmail) if err != nil { badRequest(w, malformedUserEmail) return } - jwtTokens, err := h.Api.Login(CustomerId(customerId), userEmail, loginPost.KeySelection) + jwtTokens, err := h.Api.Login(models.CustomerId(customerId), userEmail, loginPost.KeySelection) if err != nil { handleError(w, err) return @@ -243,7 +255,7 @@ func (h *NKodeHandler) RenewAttributesHandler(w http.ResponseWriter, r *http.Req methodNotAllowed(w) return } - var renewAttributesPost RenewAttributesPost + var renewAttributesPost models.RenewAttributesPost if err := decodeJson(w, r, &renewAttributesPost); err != nil { return } @@ -252,7 +264,7 @@ func (h *NKodeHandler) RenewAttributesHandler(w http.ResponseWriter, r *http.Req badRequest(w, malformedCustomerId) return } - if err = h.Api.RenewAttributes(CustomerId(customerId)); err != nil { + if err = h.Api.RenewAttributes(models.CustomerId(customerId)); err != nil { handleError(w, err) return } @@ -271,9 +283,9 @@ func (h *NKodeHandler) RandomSvgInterfaceHandler(w http.ResponseWriter, r *http. return } - respBody := RandomSvgInterfaceResp{ + respBody := models.RandomSvgInterfaceResp{ Svgs: svgs, - Colors: SetColors, + Colors: models.SetColors, } marshalAndWriteBytes(w, respBody) @@ -289,26 +301,26 @@ func (h *NKodeHandler) RefreshTokenHandler(w http.ResponseWriter, r *http.Reques forbidden(w) return } - refreshClaims, err := ParseRegisteredClaimToken(refreshToken) + refreshClaims, err := security.ParseRegisteredClaimToken(refreshToken) customerId, err := uuid.Parse(refreshClaims.Issuer) if err != nil { badRequest(w, malformedCustomerId) return } - userEmail, err := ParseEmail(refreshClaims.Subject) + userEmail, err := models.ParseEmail(refreshClaims.Subject) if err != nil { badRequest(w, malformedUserEmail) log.Println(err) return } - accessToken, err := h.Api.RefreshToken(userEmail, CustomerId(customerId), refreshToken) + accessToken, err := h.Api.RefreshToken(userEmail, models.CustomerId(customerId), refreshToken) if err != nil { handleError(w, err) log.Println(err) return } - marshalAndWriteBytes(w, RefreshTokenResp{AccessToken: accessToken}) + marshalAndWriteBytes(w, models.RefreshTokenResp{AccessToken: accessToken}) } func (h *NKodeHandler) ResetNKode(w http.ResponseWriter, r *http.Request) { @@ -316,7 +328,7 @@ func (h *NKodeHandler) ResetNKode(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { methodNotAllowed(w) } - var resetNKodePost ResetNKodePost + var resetNKodePost models.ResetNKodePost if err := decodeJson(w, r, &resetNKodePost); err != nil { return } @@ -327,13 +339,13 @@ func (h *NKodeHandler) ResetNKode(w http.ResponseWriter, r *http.Request) { return } - userEmail, err := ParseEmail(resetNKodePost.UserEmail) + userEmail, err := models.ParseEmail(resetNKodePost.UserEmail) if err != nil { badRequest(w, malformedUserEmail) return } - if err = h.Api.ResetNKode(userEmail, CustomerId(customerId)); err != nil { + if err = h.Api.ResetNKode(userEmail, models.CustomerId(customerId)); err != nil { internalServerError(w) log.Println(err) return @@ -386,7 +398,7 @@ func forbidden(w http.ResponseWriter) { func handleError(w http.ResponseWriter, err error) { log.Print("handling error: ", err) - statusCode, exists := HttpErrMap[err] + statusCode, exists := config.HttpErrMap[err] if !exists { internalServerError(w) return diff --git a/core/nkode_api.go b/internal/api/nkode_api.go similarity index 62% rename from core/nkode_api.go rename to internal/api/nkode_api.go index e4e2c15..69fe69a 100644 --- a/core/nkode_api.go +++ b/internal/api/nkode_api.go @@ -1,9 +1,13 @@ -package core +package api import ( "fmt" "github.com/google/uuid" "github.com/patrickmn/go-cache" + "go-nkode/config" + "go-nkode/internal/email" + "go-nkode/internal/models" + "go-nkode/internal/security" "log" "os" "time" @@ -17,10 +21,10 @@ const ( type NKodeAPI struct { Db DbAccessor SignupSessionCache *cache.Cache - EmailQueue *EmailQueue + EmailQueue *email.EmailQueue } -func NewNKodeAPI(db DbAccessor, queue *EmailQueue) NKodeAPI { +func NewNKodeAPI(db DbAccessor, queue *email.EmailQueue) NKodeAPI { return NKodeAPI{ Db: db, EmailQueue: queue, @@ -28,8 +32,8 @@ func NewNKodeAPI(db DbAccessor, queue *EmailQueue) NKodeAPI { } } -func (n *NKodeAPI) CreateNewCustomer(nkodePolicy NKodePolicy, id *CustomerId) (*CustomerId, error) { - newCustomer, err := NewCustomer(nkodePolicy) +func (n *NKodeAPI) CreateNewCustomer(nkodePolicy models.NKodePolicy, id *models.CustomerId) (*models.CustomerId, error) { + newCustomer, err := models.NewCustomer(nkodePolicy) if id != nil { newCustomer.Id = *id } @@ -44,20 +48,20 @@ func (n *NKodeAPI) CreateNewCustomer(nkodePolicy NKodePolicy, id *CustomerId) (* return &newCustomer.Id, nil } -func (n *NKodeAPI) GenerateSignupResetInterface(userEmail UserEmail, customerId CustomerId, kp KeypadDimension, reset bool) (*GenerateSignupResetInterfaceResp, error) { +func (n *NKodeAPI) GenerateSignupResetInterface(userEmail models.UserEmail, customerId models.CustomerId, kp models.KeypadDimension, reset bool) (*models.GenerateSignupResetInterfaceResp, error) { user, err := n.Db.GetUser(userEmail, customerId) if err != nil { return nil, err } if user != nil && !reset { log.Printf("user %s already exists", string(userEmail)) - return nil, ErrUserAlreadyExists + return nil, config.ErrUserAlreadyExists } svgIdxInterface, err := n.Db.RandomSvgIdxInterface(kp) if err != nil { return nil, err } - signupSession, err := NewSignupResetSession(userEmail, kp, customerId, svgIdxInterface, reset) + signupSession, err := models.NewSignupResetSession(userEmail, kp, customerId, svgIdxInterface, reset) if err != nil { return nil, err } @@ -70,7 +74,7 @@ func (n *NKodeAPI) GenerateSignupResetInterface(userEmail UserEmail, customerId if err != nil { return nil, err } - resp := GenerateSignupResetInterfaceResp{ + resp := models.GenerateSignupResetInterfaceResp{ UserIdxInterface: signupSession.SetIdxInterface, SvgInterface: svgInterface, SessionId: uuid.UUID(signupSession.Id).String(), @@ -79,7 +83,7 @@ func (n *NKodeAPI) GenerateSignupResetInterface(userEmail UserEmail, customerId return &resp, nil } -func (n *NKodeAPI) SetNKode(customerId CustomerId, sessionId SessionId, keySelection KeySelection) (IdxInterface, error) { +func (n *NKodeAPI) SetNKode(customerId models.CustomerId, sessionId models.SessionId, keySelection models.KeySelection) (models.IdxInterface, error) { _, err := n.Db.GetCustomer(customerId) if err != nil { @@ -88,12 +92,12 @@ func (n *NKodeAPI) SetNKode(customerId CustomerId, sessionId SessionId, keySelec session, exists := n.SignupSessionCache.Get(sessionId.String()) if !exists { log.Printf("session id does not exist %s", sessionId) - return nil, ErrSignupSessionDNE + return nil, config.ErrSignupSessionDNE } - userSession, ok := session.(UserSignSession) + userSession, ok := session.(models.UserSignSession) if !ok { // handle the case where the type assertion fails - return nil, ErrSignupSessionDNE + return nil, config.ErrSignupSessionDNE } confirmInterface, err := userSession.SetUserNKode(keySelection) if err != nil { @@ -103,16 +107,16 @@ func (n *NKodeAPI) SetNKode(customerId CustomerId, sessionId SessionId, keySelec return confirmInterface, nil } -func (n *NKodeAPI) ConfirmNKode(customerId CustomerId, sessionId SessionId, keySelection KeySelection) error { +func (n *NKodeAPI) ConfirmNKode(customerId models.CustomerId, sessionId models.SessionId, keySelection models.KeySelection) error { session, exists := n.SignupSessionCache.Get(sessionId.String()) if !exists { log.Printf("session id does not exist %s", sessionId) - return ErrSignupSessionDNE + return config.ErrSignupSessionDNE } - userSession, ok := session.(UserSignSession) + userSession, ok := session.(models.UserSignSession) if !ok { // handle the case where the type assertion fails - return ErrSignupSessionDNE + return config.ErrSignupSessionDNE } customer, err := n.Db.GetCustomer(customerId) if err != nil { @@ -125,7 +129,7 @@ func (n *NKodeAPI) ConfirmNKode(customerId CustomerId, sessionId SessionId, keyS if err = customer.IsValidNKode(userSession.Kp, passcode); err != nil { return err } - user, err := NewUser(*customer, string(userSession.UserEmail), passcode, userSession.LoginUserInterface, userSession.Kp) + user, err := models.NewUser(*customer, string(userSession.UserEmail), passcode, userSession.LoginUserInterface, userSession.Kp) if err != nil { return err } @@ -138,14 +142,14 @@ func (n *NKodeAPI) ConfirmNKode(customerId CustomerId, sessionId SessionId, keyS return err } -func (n *NKodeAPI) GetLoginInterface(userEmail UserEmail, customerId CustomerId) (*GetLoginInterfaceResp, error) { +func (n *NKodeAPI) GetLoginInterface(userEmail models.UserEmail, customerId models.CustomerId) (*models.GetLoginInterfaceResp, error) { user, err := n.Db.GetUser(userEmail, customerId) if err != nil { return nil, err } if user == nil { log.Printf("user %s for customer %s dne", userEmail, customerId) - return nil, ErrUserForCustomerDNE + return nil, config.ErrUserForCustomerDNE } err = user.Interface.PartialInterfaceShuffle() if err != nil { @@ -159,17 +163,17 @@ func (n *NKodeAPI) GetLoginInterface(userEmail UserEmail, customerId CustomerId) if err != nil { return nil, err } - resp := GetLoginInterfaceResp{ + resp := models.GetLoginInterfaceResp{ UserIdxInterface: user.Interface.IdxInterface, SvgInterface: svgInterface, NumbOfKeys: user.Kp.NumbOfKeys, AttrsPerKey: user.Kp.AttrsPerKey, - Colors: SetColors, + Colors: models.SetColors, } return &resp, nil } -func (n *NKodeAPI) Login(customerId CustomerId, userEmail UserEmail, keySelection KeySelection) (*AuthenticationTokens, error) { +func (n *NKodeAPI) Login(customerId models.CustomerId, userEmail models.UserEmail, keySelection models.KeySelection) (*security.AuthenticationTokens, error) { customer, err := n.Db.GetCustomer(customerId) if err != nil { return nil, err @@ -180,9 +184,9 @@ func (n *NKodeAPI) Login(customerId CustomerId, userEmail UserEmail, keySelectio } if user == nil { log.Printf("user %s for customer %s dne", userEmail, customerId) - return nil, ErrUserForCustomerDNE + return nil, config.ErrUserForCustomerDNE } - passcode, err := ValidKeyEntry(*user, *customer, keySelection) + passcode, err := models.ValidKeyEntry(*user, *customer, keySelection) if err != nil { return nil, err } @@ -193,7 +197,7 @@ func (n *NKodeAPI) Login(customerId CustomerId, userEmail UserEmail, keySelectio return nil, err } } - jwtToken, err := NewAuthenticationTokens(string(user.Email), customerId) + jwtToken, err := security.NewAuthenticationTokens(string(user.Email), uuid.UUID(customerId)) if err != nil { return nil, err } @@ -204,38 +208,38 @@ func (n *NKodeAPI) Login(customerId CustomerId, userEmail UserEmail, keySelectio return &jwtToken, nil } -func (n *NKodeAPI) RenewAttributes(customerId CustomerId) error { +func (n *NKodeAPI) RenewAttributes(customerId models.CustomerId) error { return n.Db.Renew(customerId) } func (n *NKodeAPI) RandomSvgInterface() ([]string, error) { - return n.Db.RandomSvgInterface(KeypadMax) + return n.Db.RandomSvgInterface(models.KeypadMax) } -func (n *NKodeAPI) RefreshToken(userEmail UserEmail, customerId CustomerId, refreshToken string) (string, error) { +func (n *NKodeAPI) RefreshToken(userEmail models.UserEmail, customerId models.CustomerId, refreshToken string) (string, error) { user, err := n.Db.GetUser(userEmail, customerId) if err != nil { return "", err } if user == nil { log.Printf("user %s for customer %s dne", userEmail, customerId) - return "", ErrUserForCustomerDNE + return "", config.ErrUserForCustomerDNE } if user.RefreshToken != refreshToken { - return "", ErrRefreshTokenInvalid + return "", config.ErrRefreshTokenInvalid } - refreshClaims, err := ParseRegisteredClaimToken(refreshToken) + refreshClaims, err := security.ParseRegisteredClaimToken(refreshToken) if err != nil { return "", err } - if err = ClaimExpired(*refreshClaims); err != nil { + if err = security.ClaimExpired(*refreshClaims); err != nil { return "", err } - newAccessClaims := NewAccessClaim(string(userEmail), customerId) - return EncodeAndSignClaims(newAccessClaims) + newAccessClaims := security.NewAccessClaim(string(userEmail), uuid.UUID(customerId)) + return security.EncodeAndSignClaims(newAccessClaims) } -func (n *NKodeAPI) ResetNKode(userEmail UserEmail, customerId CustomerId) error { +func (n *NKodeAPI) ResetNKode(userEmail models.UserEmail, customerId models.CustomerId) error { user, err := n.Db.GetUser(userEmail, customerId) if err != nil { return fmt.Errorf("error getting user in rest nkode %v", err) @@ -245,16 +249,16 @@ func (n *NKodeAPI) ResetNKode(userEmail UserEmail, customerId CustomerId) error return nil } - nkodeResetJwt, err := ResetNKodeToken(userEmail, customerId) + nkodeResetJwt, err := security.ResetNKodeToken(string(userEmail), uuid.UUID(customerId)) if err != nil { return err } frontendHost := os.Getenv("FRONTEND_HOST") if frontendHost == "" { - frontendHost = FrontendHost + frontendHost = config.FrontendHost } htmlBody := fmt.Sprintf("

Hello!

Click the link to reset your nKode.

Reset nKode", frontendHost, nkodeResetJwt) - email := Email{ + email := email.Email{ Sender: "no-reply@nkode.tech", Recipient: string(userEmail), Subject: "nKode Reset", diff --git a/core/nkode_api_test.go b/internal/api/nkode_api_test.go similarity index 60% rename from core/nkode_api_test.go rename to internal/api/nkode_api_test.go index 74d4177..80d1b1a 100644 --- a/core/nkode_api_test.go +++ b/internal/api/nkode_api_test.go @@ -1,8 +1,11 @@ -package core +package api import ( "github.com/stretchr/testify/assert" - "go-nkode/util" + "go-nkode/internal/db" + "go-nkode/internal/email" + "go-nkode/internal/models" + "go-nkode/internal/security" "os" "testing" ) @@ -13,7 +16,7 @@ func TestNKodeAPI(t *testing.T) { dbFile := os.Getenv("TEST_DB") - db2 := NewSqliteDB(dbFile) + db2 := db.NewSqliteDB(dbFile) defer db2.CloseDb() testNKodeAPI(t, db2) @@ -28,17 +31,17 @@ func TestNKodeAPI(t *testing.T) { func testNKodeAPI(t *testing.T, db DbAccessor) { bufferSize := 100 emailsPerSec := 14 - testClient := TestEmailClient{} - queue := NewEmailQueue(bufferSize, emailsPerSec, &testClient) + testClient := email.TestEmailClient{} + queue := email.NewEmailQueue(bufferSize, emailsPerSec, &testClient) queue.Start() defer queue.Stop() attrsPerKey := 5 numbOfKeys := 4 for idx := 0; idx < 1; idx++ { - userEmail := UserEmail("test_username" + util.GenerateRandomString(12) + "@example.com") + userEmail := models.UserEmail("test_username" + security.GenerateRandomString(12) + "@example.com") passcodeLen := 4 - nkodePolicy := NewDefaultNKodePolicy() - keypadSize := KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys} + nkodePolicy := models.NewDefaultNKodePolicy() + keypadSize := models.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys} nkodeApi := NewNKodeAPI(db, queue) customerId, err := nkodeApi.CreateNewCustomer(nkodePolicy, nil) assert.NoError(t, err) @@ -46,22 +49,22 @@ func testNKodeAPI(t *testing.T, db DbAccessor) { assert.NoError(t, err) setInterface := signupResponse.UserIdxInterface sessionIdStr := signupResponse.SessionId - sessionId, err := SessionIdFromString(sessionIdStr) + sessionId, err := models.SessionIdFromString(sessionIdStr) assert.NoError(t, err) - keypadSize = KeypadDimension{AttrsPerKey: numbOfKeys, NumbOfKeys: numbOfKeys} + keypadSize = models.KeypadDimension{AttrsPerKey: numbOfKeys, NumbOfKeys: numbOfKeys} userPasscode := setInterface[:passcodeLen] - setKeySelect, err := SelectKeyByAttrIdx(setInterface, userPasscode, keypadSize) + setKeySelect, err := models.SelectKeyByAttrIdx(setInterface, userPasscode, keypadSize) assert.NoError(t, err) confirmInterface, err := nkodeApi.SetNKode(*customerId, sessionId, setKeySelect) assert.NoError(t, err) - confirmKeySelect, err := SelectKeyByAttrIdx(confirmInterface, userPasscode, keypadSize) + confirmKeySelect, err := models.SelectKeyByAttrIdx(confirmInterface, userPasscode, keypadSize) err = nkodeApi.ConfirmNKode(*customerId, sessionId, confirmKeySelect) assert.NoError(t, err) - keypadSize = KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys} + keypadSize = models.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys} loginInterface, err := nkodeApi.GetLoginInterface(userEmail, *customerId) assert.NoError(t, err) - loginKeySelection, err := SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, keypadSize) + loginKeySelection, err := models.SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, keypadSize) assert.NoError(t, err) _, err = nkodeApi.Login(*customerId, userEmail, loginKeySelection) assert.NoError(t, err) @@ -71,34 +74,34 @@ func testNKodeAPI(t *testing.T, db DbAccessor) { loginInterface, err = nkodeApi.GetLoginInterface(userEmail, *customerId) assert.NoError(t, err) - loginKeySelection, err = SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, keypadSize) + loginKeySelection, err = models.SelectKeyByAttrIdx(loginInterface.UserIdxInterface, userPasscode, keypadSize) assert.NoError(t, err) _, err = nkodeApi.Login(*customerId, userEmail, loginKeySelection) assert.NoError(t, err) /// Reset nKode attrsPerKey = 6 - keypadSize = KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys} + keypadSize = models.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 = SessionIdFromString(sessionIdStr) + sessionId, err = models.SessionIdFromString(sessionIdStr) assert.NoError(t, err) - keypadSize = KeypadDimension{AttrsPerKey: numbOfKeys, NumbOfKeys: numbOfKeys} + keypadSize = models.KeypadDimension{AttrsPerKey: numbOfKeys, NumbOfKeys: numbOfKeys} userPasscode = setInterface[:passcodeLen] - setKeySelect, err = SelectKeyByAttrIdx(setInterface, userPasscode, keypadSize) + setKeySelect, err = models.SelectKeyByAttrIdx(setInterface, userPasscode, keypadSize) assert.NoError(t, err) confirmInterface, err = nkodeApi.SetNKode(*customerId, sessionId, setKeySelect) assert.NoError(t, err) - confirmKeySelect, err = SelectKeyByAttrIdx(confirmInterface, userPasscode, keypadSize) + confirmKeySelect, err = models.SelectKeyByAttrIdx(confirmInterface, userPasscode, keypadSize) err = nkodeApi.ConfirmNKode(*customerId, sessionId, confirmKeySelect) assert.NoError(t, err) - keypadSize = KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys} + keypadSize = models.KeypadDimension{AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys} loginInterface2, err := nkodeApi.GetLoginInterface(userEmail, *customerId) assert.NoError(t, err) - loginKeySelection, err = SelectKeyByAttrIdx(loginInterface2.UserIdxInterface, userPasscode, keypadSize) + loginKeySelection, err = models.SelectKeyByAttrIdx(loginInterface2.UserIdxInterface, userPasscode, keypadSize) assert.NoError(t, err) _, err = nkodeApi.Login(*customerId, userEmail, loginKeySelection) assert.NoError(t, err) diff --git a/core/in_memory_db.go b/internal/db/in_memory_db.go similarity index 60% rename from core/in_memory_db.go rename to internal/db/in_memory_db.go index 6d1c8f9..2e3197e 100644 --- a/core/in_memory_db.go +++ b/internal/db/in_memory_db.go @@ -1,25 +1,26 @@ -package core +package db import ( "errors" "fmt" + "go-nkode/internal/models" ) type InMemoryDb struct { - Customers map[CustomerId]Customer - Users map[UserId]User - userIdMap map[string]UserId + Customers map[models.CustomerId]models.Customer + Users map[models.UserId]models.User + userIdMap map[string]models.UserId } func NewInMemoryDb() InMemoryDb { return InMemoryDb{ - Customers: make(map[CustomerId]Customer), - Users: make(map[UserId]User), - userIdMap: make(map[string]UserId), + Customers: make(map[models.CustomerId]models.Customer), + Users: make(map[models.UserId]models.User), + userIdMap: make(map[string]models.UserId), } } -func (db *InMemoryDb) GetCustomer(id CustomerId) (*Customer, error) { +func (db *InMemoryDb) GetCustomer(id models.CustomerId) (*models.Customer, error) { customer, exists := db.Customers[id] if !exists { return nil, errors.New(fmt.Sprintf("customer %s dne", customer.Id)) @@ -27,7 +28,7 @@ func (db *InMemoryDb) GetCustomer(id CustomerId) (*Customer, error) { return &customer, nil } -func (db *InMemoryDb) GetUser(username UserEmail, customerId CustomerId) (*User, error) { +func (db *InMemoryDb) GetUser(username models.UserEmail, customerId models.CustomerId) (*models.User, error) { key := userIdKey(customerId, username) userId, exists := db.userIdMap[key] if !exists { @@ -40,7 +41,7 @@ func (db *InMemoryDb) GetUser(username UserEmail, customerId CustomerId) (*User, return &user, nil } -func (db *InMemoryDb) WriteNewCustomer(customer Customer) error { +func (db *InMemoryDb) WriteNewCustomer(customer models.Customer) error { _, exists := db.Customers[customer.Id] if exists { @@ -50,7 +51,7 @@ func (db *InMemoryDb) WriteNewCustomer(customer Customer) error { return nil } -func (db *InMemoryDb) WriteNewUser(user User) error { +func (db *InMemoryDb) WriteNewUser(user models.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)) @@ -66,11 +67,11 @@ func (db *InMemoryDb) WriteNewUser(user User) error { return nil } -func (db *InMemoryDb) UpdateUserNKode(user User) error { +func (db *InMemoryDb) UpdateUserNKode(user models.User) error { return errors.ErrUnsupported } -func (db *InMemoryDb) UpdateUserInterface(userId UserId, ui UserInterface) error { +func (db *InMemoryDb) UpdateUserInterface(userId models.UserId, ui models.UserInterface) error { user, exists := db.Users[userId] if !exists { return errors.New(fmt.Sprintf("can't update user %s, dne", user.Id)) @@ -80,11 +81,11 @@ func (db *InMemoryDb) UpdateUserInterface(userId UserId, ui UserInterface) error return nil } -func (db *InMemoryDb) UpdateUserRefreshToken(userId UserId, refreshToken string) error { +func (db *InMemoryDb) UpdateUserRefreshToken(userId models.UserId, refreshToken string) error { return nil } -func (db *InMemoryDb) Renew(id CustomerId) error { +func (db *InMemoryDb) Renew(id models.CustomerId) error { customer, exists := db.Customers[id] if !exists { return errors.New(fmt.Sprintf("customer %s does not exist", id)) @@ -106,7 +107,7 @@ func (db *InMemoryDb) Renew(id CustomerId) error { return nil } -func (db *InMemoryDb) RefreshUserPasscode(user User, passocode []int, customerAttr CustomerAttributes) error { +func (db *InMemoryDb) RefreshUserPasscode(user models.User, passocode []int, customerAttr models.CustomerAttributes) error { err := user.RefreshPasscode(passocode, customerAttr) if err != nil { return err @@ -115,23 +116,23 @@ func (db *InMemoryDb) RefreshUserPasscode(user User, passocode []int, customerAt return nil } -func (db *InMemoryDb) RandomSvgInterface(kp KeypadDimension) ([]string, error) { +func (db *InMemoryDb) RandomSvgInterface(kp models.KeypadDimension) ([]string, error) { return make([]string, kp.TotalAttrs()), nil } -func (db *InMemoryDb) RandomSvgIdxInterface(kp KeypadDimension) (SvgIdInterface, error) { - svgs := make(SvgIdInterface, kp.TotalAttrs()) +func (db *InMemoryDb) RandomSvgIdxInterface(kp models.KeypadDimension) (models.SvgIdInterface, error) { + svgs := make(models.SvgIdInterface, kp.TotalAttrs()) for idx := range svgs { svgs[idx] = idx } return svgs, nil } -func (db *InMemoryDb) GetSvgStringInterface(idxs SvgIdInterface) ([]string, error) { +func (db *InMemoryDb) GetSvgStringInterface(idxs models.SvgIdInterface) ([]string, error) { return make([]string, len(idxs)), nil } -func userIdKey(customerId CustomerId, username UserEmail) string { +func userIdKey(customerId models.CustomerId, username models.UserEmail) string { key := fmt.Sprintf("%s:%s", customerId, username) return key } diff --git a/core/sqlite-init/json/academicons.json b/internal/db/sqlite-init/json/academicons.json similarity index 100% rename from core/sqlite-init/json/academicons.json rename to internal/db/sqlite-init/json/academicons.json diff --git a/core/sqlite-init/json/akar-icons.json b/internal/db/sqlite-init/json/akar-icons.json similarity index 100% rename from core/sqlite-init/json/akar-icons.json rename to internal/db/sqlite-init/json/akar-icons.json diff --git a/core/sqlite-init/json/ant-design.json b/internal/db/sqlite-init/json/ant-design.json similarity index 100% rename from core/sqlite-init/json/ant-design.json rename to internal/db/sqlite-init/json/ant-design.json diff --git a/core/sqlite-init/json/arcticons.json b/internal/db/sqlite-init/json/arcticons.json similarity index 100% rename from core/sqlite-init/json/arcticons.json rename to internal/db/sqlite-init/json/arcticons.json diff --git a/core/sqlite-init/json/basil.json b/internal/db/sqlite-init/json/basil.json similarity index 100% rename from core/sqlite-init/json/basil.json rename to internal/db/sqlite-init/json/basil.json diff --git a/core/sqlite-init/json/bitcoin-icons.json b/internal/db/sqlite-init/json/bitcoin-icons.json similarity index 100% rename from core/sqlite-init/json/bitcoin-icons.json rename to internal/db/sqlite-init/json/bitcoin-icons.json diff --git a/core/sqlite-init/sqlite_init.go b/internal/db/sqlite-init/sqlite_init.go similarity index 100% rename from core/sqlite-init/sqlite_init.go rename to internal/db/sqlite-init/sqlite_init.go diff --git a/core/sqlite_db.go b/internal/db/sqlite_db.go similarity index 69% rename from core/sqlite_db.go rename to internal/db/sqlite_db.go index 969911a..a236780 100644 --- a/core/sqlite_db.go +++ b/internal/db/sqlite_db.go @@ -1,11 +1,13 @@ -package core +package db import ( "database/sql" "fmt" "github.com/google/uuid" _ "github.com/mattn/go-sqlite3" // Import the SQLite3 driver - "go-nkode/util" + "go-nkode/config" + "go-nkode/internal/models" + "go-nkode/internal/security" "log" "sync" "time" @@ -58,7 +60,7 @@ func (d *SqliteDB) CloseDb() { } } -func (d *SqliteDB) WriteNewCustomer(c Customer) error { +func (d *SqliteDB) WriteNewCustomer(c models.Customer) error { query := ` INSERT INTO customer ( id @@ -83,7 +85,7 @@ VALUES (?,?,?,?,?,?,?,?,?,?,?) return d.addWriteTx(query, args) } -func (d *SqliteDB) WriteNewUser(u User) error { +func (d *SqliteDB) WriteNewUser(u models.User) error { query := ` INSERT INTO user ( id @@ -117,16 +119,16 @@ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) 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, - util.Uint64ArrToByteArr(u.CipherKeys.AlphaKey), util.Uint64ArrToByteArr(u.CipherKeys.SetKey), - util.Uint64ArrToByteArr(u.CipherKeys.PassKey), util.Uint64ArrToByteArr(u.CipherKeys.MaskKey), - u.CipherKeys.Salt, u.CipherKeys.MaxNKodeLen, util.IntArrToByteArr(u.Interface.IdxInterface), - util.IntArrToByteArr(u.Interface.SvgId), timeStamp(), + 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(), } return d.addWriteTx(query, args) } -func (d *SqliteDB) UpdateUserNKode(u User) error { +func (d *SqliteDB) UpdateUserNKode(u models.User) error { query := ` UPDATE user SET renew = ? @@ -151,21 +153,21 @@ WHERE email = ? AND customer_id = ? } else { renew = 0 } - args := []any{renew, u.RefreshToken, u.EncipheredPasscode.Code, u.EncipheredPasscode.Mask, u.Kp.AttrsPerKey, u.Kp.NumbOfKeys, util.Uint64ArrToByteArr(u.CipherKeys.AlphaKey), util.Uint64ArrToByteArr(u.CipherKeys.SetKey), util.Uint64ArrToByteArr(u.CipherKeys.PassKey), util.Uint64ArrToByteArr(u.CipherKeys.MaskKey), u.CipherKeys.Salt, u.CipherKeys.MaxNKodeLen, util.IntArrToByteArr(u.Interface.IdxInterface), util.IntArrToByteArr(u.Interface.SvgId), string(u.Email), uuid.UUID(u.CustomerId)} + 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) } -func (d *SqliteDB) UpdateUserInterface(id UserId, ui UserInterface) error { +func (d *SqliteDB) UpdateUserInterface(id models.UserId, ui models.UserInterface) error { query := ` UPDATE user SET idx_interface = ?, last_login = ? WHERE id = ? ` - args := []any{util.IntArrToByteArr(ui.IdxInterface), timeStamp(), uuid.UUID(id).String()} + args := []any{security.IntArrToByteArr(ui.IdxInterface), timeStamp(), uuid.UUID(id).String()} return d.addWriteTx(query, args) } -func (d *SqliteDB) UpdateUserRefreshToken(id UserId, refreshToken string) error { +func (d *SqliteDB) UpdateUserRefreshToken(id models.UserId, refreshToken string) error { query := ` UPDATE user SET refresh_token = ? WHERE id = ? ` @@ -174,7 +176,7 @@ UPDATE user SET refresh_token = ? WHERE id = ? return d.addWriteTx(query, args) } -func (d *SqliteDB) Renew(id CustomerId) error { +func (d *SqliteDB) Renew(id models.CustomerId) error { // TODO: How long does a renew take? customer, err := d.GetCustomer(id) if err != nil { @@ -184,7 +186,7 @@ func (d *SqliteDB) Renew(id CustomerId) error { if err != nil { return err } - renewArgs := []any{util.Uint64ArrToByteArr(customer.Attributes.AttrVals), util.Uint64ArrToByteArr(customer.Attributes.SetVals), uuid.UUID(customer.Id).String()} + renewArgs := []any{security.Uint64ArrToByteArr(customer.Attributes.AttrVals), security.Uint64ArrToByteArr(customer.Attributes.SetVals), uuid.UUID(customer.Id).String()} // TODO: replace with tx renewQuery := ` UPDATE customer @@ -217,20 +219,20 @@ WHERE customer_id = ? if err != nil { return err } - user := User{ - Id: UserId{}, - CustomerId: CustomerId{}, + user := models.User{ + Id: models.UserId{}, + CustomerId: models.CustomerId{}, Email: "", - EncipheredPasscode: EncipheredNKode{}, - Kp: KeypadDimension{ + EncipheredPasscode: models.EncipheredNKode{}, + Kp: models.KeypadDimension{ AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys, }, - CipherKeys: UserCipherKeys{ - AlphaKey: util.ByteArrToUint64Arr(alphaBytes), - SetKey: util.ByteArrToUint64Arr(setBytes), + CipherKeys: models.UserCipherKeys{ + AlphaKey: security.ByteArrToUint64Arr(alphaBytes), + SetKey: security.ByteArrToUint64Arr(setBytes), }, - Interface: UserInterface{}, + Interface: models.UserInterface{}, Renew: false, } err = user.RenewKeys(setXor, attrXor) @@ -242,7 +244,7 @@ UPDATE user SET alpha_key = ?, set_key = ?, renew = ? WHERE id = ?; ` - renewArgs = append(renewArgs, util.Uint64ArrToByteArr(user.CipherKeys.AlphaKey), util.Uint64ArrToByteArr(user.CipherKeys.SetKey), 1, userId) + renewArgs = append(renewArgs, security.Uint64ArrToByteArr(user.CipherKeys.AlphaKey), security.Uint64ArrToByteArr(user.CipherKeys.SetKey), 1, userId) } renewQuery += ` ` @@ -253,7 +255,7 @@ WHERE id = ?; return d.addWriteTx(renewQuery, renewArgs) } -func (d *SqliteDB) RefreshUserPasscode(user User, passcodeIdx []int, customerAttr CustomerAttributes) error { +func (d *SqliteDB) RefreshUserPasscode(user models.User, passcodeIdx []int, customerAttr models.CustomerAttributes) error { err := user.RefreshPasscode(passcodeIdx, customerAttr) if err != nil { return err @@ -271,10 +273,10 @@ SET ,salt = ? WHERE id = ?; ` - args := []any{user.RefreshToken, 0, user.EncipheredPasscode.Code, user.EncipheredPasscode.Mask, util.Uint64ArrToByteArr(user.CipherKeys.AlphaKey), util.Uint64ArrToByteArr(user.CipherKeys.SetKey), util.Uint64ArrToByteArr(user.CipherKeys.PassKey), util.Uint64ArrToByteArr(user.CipherKeys.MaskKey), user.CipherKeys.Salt, uuid.UUID(user.Id).String()} + args := []any{user.RefreshToken, 0, user.EncipheredPasscode.Code, user.EncipheredPasscode.Mask, security.Uint64ArrToByteArr(user.CipherKeys.AlphaKey), security.Uint64ArrToByteArr(user.CipherKeys.SetKey), security.Uint64ArrToByteArr(user.CipherKeys.PassKey), security.Uint64ArrToByteArr(user.CipherKeys.MaskKey), user.CipherKeys.Salt, uuid.UUID(user.Id).String()} return d.addWriteTx(query, args) } -func (d *SqliteDB) GetCustomer(id CustomerId) (*Customer, error) { +func (d *SqliteDB) GetCustomer(id models.CustomerId) (*models.Customer, error) { tx, err := d.db.Begin() if err != nil { return nil, err @@ -307,7 +309,7 @@ WHERE id = ? if !rows.Next() { log.Printf("no new row for customer %s with err %s", id, rows.Err()) - return nil, ErrCustomerDne + return nil, config.ErrCustomerDne } var maxNKodeLen int @@ -322,9 +324,9 @@ WHERE id = ? if err != nil { return nil, err } - customer := Customer{ + customer := models.Customer{ Id: id, - NKodePolicy: NKodePolicy{ + NKodePolicy: models.NKodePolicy{ MaxNkodeLen: maxNKodeLen, MinNkodeLen: minNKodeLen, DistinctSets: distinctSets, @@ -332,7 +334,7 @@ WHERE id = ? LockOut: lockOut, Expiration: expiration, }, - Attributes: NewCustomerAttributesFromBytes(attributeValues, setValues), + Attributes: models.NewCustomerAttributesFromBytes(attributeValues, setValues), } if err = tx.Commit(); err != nil { return nil, err @@ -340,7 +342,7 @@ WHERE id = ? return &customer, nil } -func (d *SqliteDB) GetUser(email UserEmail, customerId CustomerId) (*User, error) { +func (d *SqliteDB) GetUser(email models.UserEmail, customerId models.CustomerId) (*models.User, error) { tx, err := d.db.Begin() if err != nil { return nil, err @@ -399,30 +401,30 @@ WHERE user.email = ? AND user.customer_id = ? renew = true } - user := User{ - Id: UserId(userId), + user := models.User{ + Id: models.UserId(userId), CustomerId: customerId, Email: email, - EncipheredPasscode: EncipheredNKode{ + EncipheredPasscode: models.EncipheredNKode{ Code: code, Mask: mask, }, - Kp: KeypadDimension{ + Kp: models.KeypadDimension{ AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys, }, - CipherKeys: UserCipherKeys{ - AlphaKey: util.ByteArrToUint64Arr(alphaKey), - SetKey: util.ByteArrToUint64Arr(setKey), - PassKey: util.ByteArrToUint64Arr(passKey), - MaskKey: util.ByteArrToUint64Arr(maskKey), + 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: UserInterface{ - IdxInterface: util.ByteArrToIntArr(idxInterface), - SvgId: util.ByteArrToIntArr(svgIdInterface), + Interface: models.UserInterface{ + IdxInterface: security.ByteArrToIntArr(idxInterface), + SvgId: security.ByteArrToIntArr(svgIdInterface), Kp: nil, }, Renew: renew, @@ -436,7 +438,7 @@ WHERE user.email = ? AND user.customer_id = ? return &user, nil } -func (d *SqliteDB) RandomSvgInterface(kp KeypadDimension) ([]string, error) { +func (d *SqliteDB) RandomSvgInterface(kp models.KeypadDimension) ([]string, error) { ids, err := d.getRandomIds(kp.TotalAttrs()) if err != nil { return nil, err @@ -444,11 +446,11 @@ func (d *SqliteDB) RandomSvgInterface(kp KeypadDimension) ([]string, error) { return d.getSvgsById(ids) } -func (d *SqliteDB) RandomSvgIdxInterface(kp KeypadDimension) (SvgIdInterface, error) { +func (d *SqliteDB) RandomSvgIdxInterface(kp models.KeypadDimension) (models.SvgIdInterface, error) { return d.getRandomIds(kp.TotalAttrs()) } -func (d *SqliteDB) GetSvgStringInterface(idxs SvgIdInterface) ([]string, error) { +func (d *SqliteDB) GetSvgStringInterface(idxs models.SvgIdInterface) ([]string, error) { return d.getSvgsById(idxs) } @@ -470,7 +472,7 @@ WHERE id = ? } if !rows.Next() { log.Printf("id not found: %d", id) - return nil, ErrSvgDne + return nil, config.ErrSvgDne } if err = rows.Scan(&svgs[idx]); err != nil { return nil, err @@ -506,7 +508,7 @@ func (d *SqliteDB) writeToDb(query string, args []any) error { func (d *SqliteDB) addWriteTx(query string, args []any) error { if d.stop { - return ErrStoppingDatabase + return config.ErrStoppingDatabase } errChan := make(chan error) writeTx := WriteTx{ @@ -523,23 +525,23 @@ func (d *SqliteDB) getRandomIds(count int) ([]int, error) { tx, err := d.db.Begin() if err != nil { log.Print(err) - return nil, ErrSqliteTx + return nil, config.ErrSqliteTx } rows, err := tx.Query("SELECT COUNT(*) as count FROM svg_icon;") if err != nil { log.Print(err) - return nil, ErrSqliteTx + return nil, config.ErrSqliteTx } var tableLen int if !rows.Next() { - return nil, ErrEmptySvgTable + return nil, config.ErrEmptySvgTable } if err = rows.Scan(&tableLen); err != nil { log.Print(err) - return nil, ErrSqliteTx + return nil, config.ErrSqliteTx } - perm, err := util.RandomPermutation(tableLen) + perm, err := security.RandomPermutation(tableLen) if err != nil { return nil, err @@ -551,7 +553,7 @@ func (d *SqliteDB) getRandomIds(count int) ([]int, error) { if err = tx.Commit(); err != nil { log.Print(err) - return nil, ErrSqliteTx + return nil, config.ErrSqliteTx } return perm[:count], nil diff --git a/core/sqlite_db_test.go b/internal/db/sqlite_db_test.go similarity index 62% rename from core/sqlite_db_test.go rename to internal/db/sqlite_db_test.go index de1916a..82a5cac 100644 --- a/core/sqlite_db_test.go +++ b/internal/db/sqlite_db_test.go @@ -1,7 +1,9 @@ -package core +package db import ( "github.com/stretchr/testify/assert" + "go-nkode/internal/api" + "go-nkode/internal/models" "os" "testing" ) @@ -22,9 +24,9 @@ func TestNewSqliteDB(t *testing.T) { // } } -func testSignupLoginRenew(t *testing.T, db DbAccessor) { - nkodePolicy := NewDefaultNKodePolicy() - customerOrig, err := NewCustomer(nkodePolicy) +func testSignupLoginRenew(t *testing.T, db api.DbAccessor) { + nkodePolicy := models.NewDefaultNKodePolicy() + customerOrig, err := models.NewCustomer(nkodePolicy) assert.NoError(t, err) err = db.WriteNewCustomer(*customerOrig) assert.NoError(t, err) @@ -32,16 +34,16 @@ func testSignupLoginRenew(t *testing.T, db DbAccessor) { assert.NoError(t, err) assert.Equal(t, customerOrig, customer) username := "test_user@example.com" - kp := KeypadDefault + kp := models.KeypadDefault passcodeIdx := []int{0, 1, 2, 3} - mockSvgInterface := make(SvgIdInterface, kp.TotalAttrs()) - ui, err := NewUserInterface(&kp, mockSvgInterface) + mockSvgInterface := make(models.SvgIdInterface, kp.TotalAttrs()) + ui, err := models.NewUserInterface(&kp, mockSvgInterface) assert.NoError(t, err) - userOrig, err := NewUser(*customer, username, passcodeIdx, *ui, kp) + userOrig, err := models.NewUser(*customer, username, passcodeIdx, *ui, kp) assert.NoError(t, err) err = db.WriteNewUser(*userOrig) assert.NoError(t, err) - user, err := db.GetUser(UserEmail(username), customer.Id) + user, err := db.GetUser(models.UserEmail(username), customer.Id) assert.NoError(t, err) assert.Equal(t, userOrig, user) @@ -50,8 +52,8 @@ func testSignupLoginRenew(t *testing.T, db DbAccessor) { } -func testSqliteDBRandomSvgInterface(t *testing.T, db DbAccessor) { - kp := KeypadMax +func testSqliteDBRandomSvgInterface(t *testing.T, db api.DbAccessor) { + kp := models.KeypadMax svgs, err := db.RandomSvgInterface(kp) assert.NoError(t, err) assert.Len(t, svgs, kp.TotalAttrs()) diff --git a/core/email_queue.go b/internal/email/queue.go similarity index 98% rename from core/email_queue.go rename to internal/email/queue.go index 429bf58..3480157 100644 --- a/core/email_queue.go +++ b/internal/email/queue.go @@ -1,4 +1,4 @@ -package core +package email import ( "context" @@ -8,6 +8,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/ses" "github.com/aws/aws-sdk-go-v2/service/ses/types" "github.com/patrickmn/go-cache" + config2 "go-nkode/config" "log" "sync" "time" @@ -52,7 +53,7 @@ func NewSESClient() SESClient { func (s *SESClient) SendEmail(email Email) error { if _, exists := s.ResetCache.Get(email.Recipient); exists { log.Printf("email already sent to %s with subject %s", email.Recipient, email.Subject) - return ErrEmailAlreadySent + return config2.ErrEmailAlreadySent } // Load AWS configuration diff --git a/core/email_queue_test.go b/internal/email/queue_test.go similarity index 97% rename from core/email_queue_test.go rename to internal/email/queue_test.go index ff0ab49..525f06a 100644 --- a/core/email_queue_test.go +++ b/internal/email/queue_test.go @@ -1,4 +1,4 @@ -package core +package email import ( "fmt" diff --git a/core/customer.go b/internal/models/customer.go similarity index 77% rename from core/customer.go rename to internal/models/customer.go index 2cc3c28..3183368 100644 --- a/core/customer.go +++ b/internal/models/customer.go @@ -1,9 +1,10 @@ -package core +package models import ( "github.com/google/uuid" - "go-nkode/hashset" - "go-nkode/util" + "go-nkode/config" + "go-nkode/internal/security" + "go-nkode/internal/utils" ) type Customer struct { @@ -29,14 +30,14 @@ func NewCustomer(nkodePolicy NKodePolicy) (*Customer, error) { func (c *Customer) IsValidNKode(kp KeypadDimension, passcodeAttrIdx []int) error { nkodeLen := len(passcodeAttrIdx) if nkodeLen < c.NKodePolicy.MinNkodeLen || nkodeLen > c.NKodePolicy.MaxNkodeLen { - return ErrInvalidNKodeLength + return config.ErrInvalidNKodeLength } if validIdx := kp.ValidateAttributeIndices(passcodeAttrIdx); !validIdx { - return ErrInvalidNKodeIdx + return config.ErrInvalidNKodeIdx } - passcodeSetVals := make(hashset.Set[uint64]) - passcodeAttrVals := make(hashset.Set[uint64]) + passcodeSetVals := make(utils.Set[uint64]) + passcodeAttrVals := make(utils.Set[uint64]) attrVals, err := c.Attributes.AttrValsForKp(kp) if err != nil { return err @@ -52,11 +53,11 @@ func (c *Customer) IsValidNKode(kp KeypadDimension, passcodeAttrIdx []int) error } if passcodeSetVals.Size() < c.NKodePolicy.DistinctSets { - return ErrTooFewDistinctSet + return config.ErrTooFewDistinctSet } if passcodeAttrVals.Size() < c.NKodePolicy.DistinctAttributes { - return ErrTooFewDistinctAttributes + return config.ErrTooFewDistinctAttributes } return nil } @@ -71,11 +72,11 @@ func (c *Customer) RenewKeys() ([]uint64, []uint64, error) { if err := c.Attributes.Renew(); err != nil { return nil, nil, err } - attrsXor, err := util.XorLists(oldAttrs, c.Attributes.AttrVals) + attrsXor, err := security.XorLists(oldAttrs, c.Attributes.AttrVals) if err != nil { return nil, nil, err } - setXor, err := util.XorLists(oldSets, c.Attributes.SetVals) + setXor, err := security.XorLists(oldSets, c.Attributes.SetVals) if err != nil { return nil, nil, err } diff --git a/core/customer_attributes.go b/internal/models/customer_attributes.go similarity index 72% rename from core/customer_attributes.go rename to internal/models/customer_attributes.go index e25b5d2..64827d9 100644 --- a/core/customer_attributes.go +++ b/internal/models/customer_attributes.go @@ -1,7 +1,7 @@ -package core +package models import ( - "go-nkode/util" + "go-nkode/internal/security" "log" ) @@ -11,12 +11,12 @@ type CustomerAttributes struct { } func NewCustomerAttributes() (*CustomerAttributes, error) { - attrVals, err := util.GenerateRandomNonRepeatingUint64(KeypadMax.TotalAttrs()) + attrVals, err := security.GenerateRandomNonRepeatingUint64(KeypadMax.TotalAttrs()) if err != nil { log.Print("unable to generate attribute vals: ", err) return nil, err } - setVals, err := util.GenerateRandomNonRepeatingUint64(KeypadMax.AttrsPerKey) + setVals, err := security.GenerateRandomNonRepeatingUint64(KeypadMax.AttrsPerKey) if err != nil { log.Print("unable to generate set vals: ", err) return nil, err @@ -31,17 +31,17 @@ func NewCustomerAttributes() (*CustomerAttributes, error) { func NewCustomerAttributesFromBytes(attrBytes []byte, setBytes []byte) CustomerAttributes { return CustomerAttributes{ - AttrVals: util.ByteArrToUint64Arr(attrBytes), - SetVals: util.ByteArrToUint64Arr(setBytes), + AttrVals: security.ByteArrToUint64Arr(attrBytes), + SetVals: security.ByteArrToUint64Arr(setBytes), } } func (c *CustomerAttributes) Renew() error { - attrVals, err := util.GenerateRandomNonRepeatingUint64(KeypadMax.TotalAttrs()) + attrVals, err := security.GenerateRandomNonRepeatingUint64(KeypadMax.TotalAttrs()) if err != nil { return err } - setVals, err := util.GenerateRandomNonRepeatingUint64(KeypadMax.AttrsPerKey) + setVals, err := security.GenerateRandomNonRepeatingUint64(KeypadMax.AttrsPerKey) if err != nil { return err } @@ -52,12 +52,12 @@ func (c *CustomerAttributes) Renew() error { func (c *CustomerAttributes) IndexOfAttr(attrVal uint64) (int, error) { // TODO: should this be mapped instead? - return util.IndexOf[uint64](c.AttrVals, attrVal) + return security.IndexOf[uint64](c.AttrVals, attrVal) } func (c *CustomerAttributes) IndexOfSet(setVal uint64) (int, error) { // TODO: should this be mapped instead? - return util.IndexOf[uint64](c.SetVals, setVal) + return security.IndexOf[uint64](c.SetVals, setVal) } func (c *CustomerAttributes) GetAttrSetVal(attrVal uint64, userKeypad KeypadDimension) (uint64, error) { @@ -86,9 +86,9 @@ func (c *CustomerAttributes) SetValsForKp(userKp KeypadDimension) ([]uint64, err } func (c *CustomerAttributes) AttrBytes() []byte { - return util.Uint64ArrToByteArr(c.AttrVals) + return security.Uint64ArrToByteArr(c.AttrVals) } func (c *CustomerAttributes) SetBytes() []byte { - return util.Uint64ArrToByteArr(c.SetVals) + return security.Uint64ArrToByteArr(c.SetVals) } diff --git a/core/customer_test.go b/internal/models/customer_test.go similarity index 99% rename from core/customer_test.go rename to internal/models/customer_test.go index 610bb0d..1b15fdb 100644 --- a/core/customer_test.go +++ b/internal/models/customer_test.go @@ -1,4 +1,4 @@ -package core +package models import ( "github.com/stretchr/testify/assert" diff --git a/core/keypad_dimension.go b/internal/models/keypad_dimension.go similarity index 91% rename from core/keypad_dimension.go rename to internal/models/keypad_dimension.go index fbc6b84..75ccfed 100644 --- a/core/keypad_dimension.go +++ b/internal/models/keypad_dimension.go @@ -1,7 +1,8 @@ -package core +package models import ( - py "go-nkode/py-builtin" + "go-nkode/config" + py "go-nkode/internal/utils" ) type KeypadDimension struct { @@ -19,7 +20,7 @@ func (kp *KeypadDimension) IsDispersable() bool { func (kp *KeypadDimension) IsValidKeypadDimension() error { if KeypadMin.AttrsPerKey > kp.AttrsPerKey || KeypadMax.AttrsPerKey < kp.AttrsPerKey || KeypadMin.NumbOfKeys > kp.NumbOfKeys || KeypadMax.NumbOfKeys < kp.NumbOfKeys { - return ErrInvalidKeypadDimensions + return config.ErrInvalidKeypadDimensions } return nil } diff --git a/core/type.go b/internal/models/models.go similarity index 84% rename from core/type.go rename to internal/models/models.go index 5f20d22..87a8832 100644 --- a/core/type.go +++ b/internal/models/models.go @@ -1,4 +1,4 @@ -package core +package models import ( "github.com/google/uuid" @@ -88,6 +88,7 @@ type GetLoginInterfaceResp struct { } type KeySelection []int + type CustomerId uuid.UUID func CustomerIdToString(customerId CustomerId) string { @@ -138,17 +139,21 @@ type RGBColor struct { Blue int `json:"blue"` } -type DbAccessor interface { - GetCustomer(CustomerId) (*Customer, error) - GetUser(UserEmail, CustomerId) (*User, error) - WriteNewCustomer(Customer) error - WriteNewUser(User) error - UpdateUserNKode(User) error - UpdateUserInterface(UserId, UserInterface) error - UpdateUserRefreshToken(UserId, string) error - Renew(CustomerId) error - RefreshUserPasscode(User, []int, CustomerAttributes) error - RandomSvgInterface(KeypadDimension) ([]string, error) - RandomSvgIdxInterface(KeypadDimension) (SvgIdInterface, error) - GetSvgStringInterface(SvgIdInterface) ([]string, error) +var SetColors = []RGBColor{ + {0, 0, 0}, // Black + {255, 0, 0}, // Red + {0, 128, 0}, // Dark Green + {0, 0, 255}, // Blue + {244, 200, 60}, // Yellow + {255, 0, 255}, // Magenta + {0, 200, 200}, // Cyan + {127, 0, 127}, // Purple + {232, 92, 13}, // Orange + {0, 127, 127}, // Teal + {127, 127, 0}, // Olive + {127, 0, 0}, // Dark Red + {128, 128, 128}, // Gray + {228, 102, 102}, // Dark Purple + {185, 17, 240}, // Salmon + {16, 200, 100}, // Green } diff --git a/core/nkode_policy.go b/internal/models/policy.go similarity index 90% rename from core/nkode_policy.go rename to internal/models/policy.go index 607f1d6..7c694bc 100644 --- a/core/nkode_policy.go +++ b/internal/models/policy.go @@ -1,4 +1,6 @@ -package core +package models + +import "go-nkode/config" type NKodePolicy struct { MaxNkodeLen int `json:"max_nkode_len"` @@ -23,7 +25,7 @@ func NewDefaultNKodePolicy() NKodePolicy { func (p *NKodePolicy) ValidLength(nkodeLen int) error { if nkodeLen < p.MinNkodeLen || nkodeLen > p.MaxNkodeLen { - return ErrInvalidNKodeLength + return config.ErrInvalidNKodeLength } // TODO: validate Max > Min // Validate lockout diff --git a/core/test_helper.go b/internal/models/test_helper.go similarity index 81% rename from core/test_helper.go rename to internal/models/test_helper.go index 273c315..33cea57 100644 --- a/core/test_helper.go +++ b/internal/models/test_helper.go @@ -1,15 +1,15 @@ -package core +package models import ( "errors" "fmt" - "go-nkode/util" + "go-nkode/internal/security" ) func SelectKeyByAttrIdx(interfaceUser []int, passcodeIdxs []int, keypadSize KeypadDimension) ([]int, error) { selectedKeys := make([]int, len(passcodeIdxs)) for idx := range passcodeIdxs { - attrIdx, err := util.IndexOf[int](interfaceUser, passcodeIdxs[idx]) + attrIdx, err := security.IndexOf[int](interfaceUser, passcodeIdxs[idx]) if err != nil { return nil, err } diff --git a/core/user.go b/internal/models/user.go similarity index 88% rename from core/user.go rename to internal/models/user.go index 618fed2..86847e3 100644 --- a/core/user.go +++ b/internal/models/user.go @@ -1,8 +1,9 @@ -package core +package models import ( "github.com/google/uuid" - "go-nkode/util" + "go-nkode/config" + "go-nkode/internal/security" "log" ) @@ -25,11 +26,11 @@ func (u *User) DecipherMask(setVals []uint64, passcodeLen int) ([]uint64, error) func (u *User) RenewKeys(setXor []uint64, attrXor []uint64) error { u.Renew = true var err error - u.CipherKeys.SetKey, err = util.XorLists(setXor[:u.Kp.AttrsPerKey], u.CipherKeys.SetKey) + u.CipherKeys.SetKey, err = security.XorLists(setXor[:u.Kp.AttrsPerKey], u.CipherKeys.SetKey) if err != nil { return err } - u.CipherKeys.AlphaKey, err = util.XorLists(attrXor[:u.Kp.TotalAttrs()], u.CipherKeys.AlphaKey) + u.CipherKeys.AlphaKey, err = security.XorLists(attrXor[:u.Kp.TotalAttrs()], u.CipherKeys.AlphaKey) return err } @@ -63,7 +64,7 @@ func (u *User) GetLoginInterface() ([]int, error) { func ValidKeyEntry(user User, customer Customer, selectedKeys []int) ([]int, error) { if validKeys := user.Kp.ValidKeySelections(selectedKeys); !validKeys { - return nil, ErrKeyIndexOutOfRange + return nil, config.ErrKeyIndexOutOfRange } passcodeLen := len(selectedKeys) @@ -74,13 +75,13 @@ func ValidKeyEntry(user User, customer Customer, selectedKeys []int) ([]int, err setVals, err := customer.Attributes.SetValsForKp(user.Kp) if err != nil { log.Printf("fatal error in validate key entry;invalid user keypad dimensions for user %s with error %v", user.Email, err) - return nil, ErrInternalValidKeyEntry + return nil, config.ErrInternalValidKeyEntry } passcodeSetVals, err := user.DecipherMask(setVals, passcodeLen) if err != nil { log.Printf("fatal error in validate key entry;something when wrong deciphering mask;user email %s; error %v", user.Email, err) - return nil, ErrInternalValidKeyEntry + return nil, config.ErrInternalValidKeyEntry } presumedAttrIdxVals := make([]int, passcodeLen) @@ -89,12 +90,12 @@ func ValidKeyEntry(user User, customer Customer, selectedKeys []int) ([]int, err setIdx, err := customer.Attributes.IndexOfSet(passcodeSetVals[idx]) if err != nil { log.Printf("fatal error in validate key entry;something when wrong getting the IndexOfSet;user email %s; error %v", user.Email, err) - return nil, ErrInternalValidKeyEntry + return nil, config.ErrInternalValidKeyEntry } selectedAttrIdx, err := user.Interface.GetAttrIdxByKeyNumbSetIdx(setIdx, keyNumb) if err != nil { log.Printf("fatal error in validate key entry;something when wrong getting the GetAttrIdxByKeyNumbSetIdx;user email %s; error %v", user.Email, err) - return nil, ErrInternalValidKeyEntry + return nil, config.ErrInternalValidKeyEntry } presumedAttrIdxVals[idx] = selectedAttrIdx } diff --git a/core/user_cipher_keys.go b/internal/models/user_cipher_keys.go similarity index 84% rename from core/user_cipher_keys.go rename to internal/models/user_cipher_keys.go index 902a930..7f187a9 100644 --- a/core/user_cipher_keys.go +++ b/internal/models/user_cipher_keys.go @@ -1,9 +1,10 @@ -package core +package models import ( "crypto/sha256" "errors" - "go-nkode/util" + "go-nkode/config" + "go-nkode/internal/security" "golang.org/x/crypto/bcrypt" ) @@ -22,19 +23,19 @@ func NewUserCipherKeys(kp *KeypadDimension, setVals []uint64, maxNKodeLen int) ( if err != nil { return nil, err } - setKey, err := util.GenerateRandomNonRepeatingUint64(kp.AttrsPerKey) + setKey, err := security.GenerateRandomNonRepeatingUint64(kp.AttrsPerKey) if err != nil { return nil, err } - setKey, err = util.XorLists(setKey, setVals) + setKey, err = security.XorLists(setKey, setVals) if err != nil { return nil, err } - alphaKey, _ := util.GenerateRandomNonRepeatingUint64(kp.TotalAttrs()) - passKey, _ := util.GenerateRandomNonRepeatingUint64(maxNKodeLen) - maskKey, _ := util.GenerateRandomNonRepeatingUint64(maxNKodeLen) - salt, _ := util.RandomBytes(10) + alphaKey, _ := security.GenerateRandomNonRepeatingUint64(kp.TotalAttrs()) + passKey, _ := security.GenerateRandomNonRepeatingUint64(maxNKodeLen) + maskKey, _ := security.GenerateRandomNonRepeatingUint64(maxNKodeLen) + salt, _ := security.RandomBytes(10) userCipherKeys := UserCipherKeys{ AlphaKey: alphaKey, PassKey: passKey, @@ -49,7 +50,7 @@ func NewUserCipherKeys(kp *KeypadDimension, setVals []uint64, maxNKodeLen int) ( func (u *UserCipherKeys) PadUserMask(userMask []uint64, setVals []uint64) ([]uint64, error) { if len(userMask) > u.MaxNKodeLen { - return nil, ErrUserMaskTooLong + return nil, config.ErrUserMaskTooLong } paddedUserMask := make([]uint64, len(userMask)) copy(paddedUserMask, userMask) @@ -66,7 +67,7 @@ func (u *UserCipherKeys) ValidPassword(hashedPassword string, passcodeAttrIdx [] err := bcrypt.CompareHashAndPassword(hashBytes, passwordDigest) if err != nil { if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) { - return ErrInvalidNKode + return config.ErrInvalidNKode } return err } @@ -99,7 +100,7 @@ func (u *UserCipherKeys) encipherCode(passcodeAttrIdx []int, attrVals []uint64) } func (u *UserCipherKeys) saltAndDigest(passcode []uint64) []byte { - passcodeBytes := util.Uint64ArrToByteArr(passcode) + passcodeBytes := security.Uint64ArrToByteArr(passcode) passcodeBytes = append(passcodeBytes, u.Salt...) h := sha256.New() h.Write(passcodeBytes) @@ -136,27 +137,27 @@ func (u *UserCipherKeys) EncipherMask(passcodeSet []uint64, customerAttrs Custom setVal := paddedPasscodeSets[idx] cipheredMask[idx] = setKeyVal ^ maskKeyVal ^ setVal } - mask := util.EncodeBase64Str(cipheredMask) + mask := security.EncodeBase64Str(cipheredMask) return mask, nil } func (u *UserCipherKeys) DecipherMask(mask string, setVals []uint64, passcodeLen int) ([]uint64, error) { - decodedMask, err := util.DecodeBase64Str(mask) + decodedMask, err := security.DecodeBase64Str(mask) if err != nil { return nil, err } - decipheredMask, err := util.XorLists(decodedMask, u.MaskKey) + decipheredMask, err := security.XorLists(decodedMask, u.MaskKey) if err != nil { return nil, err } - setKeyRandComponent, err := util.XorLists(setVals, u.SetKey) + setKeyRandComponent, err := security.XorLists(setVals, u.SetKey) if err != nil { return nil, err } passcodeSet := make([]uint64, passcodeLen) for idx, setCipher := range decipheredMask[:passcodeLen] { - setIdx, err := util.IndexOf(setKeyRandComponent, setCipher) + setIdx, err := security.IndexOf(setKeyRandComponent, setCipher) if err != nil { return nil, err } diff --git a/core/user_interface.go b/internal/models/user_interface.go similarity index 65% rename from core/user_interface.go rename to internal/models/user_interface.go index 7ec856f..d5ba0a3 100644 --- a/core/user_interface.go +++ b/internal/models/user_interface.go @@ -1,8 +1,9 @@ -package core +package models import ( - "go-nkode/hashset" - "go-nkode/util" + "go-nkode/config" + "go-nkode/internal/security" + "go-nkode/internal/utils" "log" ) @@ -13,7 +14,7 @@ type UserInterface struct { } func NewUserInterface(kp *KeypadDimension, svgId SvgIdInterface) (*UserInterface, error) { - idxInterface := util.IdentityArray(kp.TotalAttrs()) + idxInterface := security.IdentityArray(kp.TotalAttrs()) userInterface := UserInterface{ IdxInterface: idxInterface, SvgId: svgId, @@ -32,29 +33,29 @@ func (u *UserInterface) RandomShuffle() error { if err != nil { return err } - setView, err := util.MatrixTranspose(keypadView) + setView, err := security.MatrixTranspose(keypadView) if err != nil { return err } for idx, set := range setView { - err := util.FisherYatesShuffle(&set) + err := security.FisherYatesShuffle(&set) if err != nil { return nil } setView[idx] = set } - keypadView, err = util.MatrixTranspose(setView) + keypadView, err = security.MatrixTranspose(setView) if err != nil { return err } - u.IdxInterface = util.MatrixToList(keypadView) + u.IdxInterface = security.MatrixToList(keypadView) return nil } func (u *UserInterface) InterfaceMatrix() ([][]int, error) { - return util.ListToMatrix(u.IdxInterface, u.Kp.AttrsPerKey) + return security.ListToMatrix(u.IdxInterface, u.Kp.AttrsPerKey) } func (u *UserInterface) SetViewMatrix() ([][]int, error) { @@ -62,12 +63,12 @@ func (u *UserInterface) SetViewMatrix() ([][]int, error) { if err != nil { return nil, err } - return util.MatrixTranspose(keypadView) + return security.MatrixTranspose(keypadView) } func (u *UserInterface) DisperseInterface() error { if !u.Kp.IsDispersable() { - return ErrInterfaceNotDispersible + return config.ErrInterfaceNotDispersible } err := u.shuffleKeys() @@ -83,15 +84,15 @@ func (u *UserInterface) DisperseInterface() error { } func (u *UserInterface) shuffleKeys() error { - userInterfaceMatrix, err := util.ListToMatrix(u.IdxInterface, u.Kp.AttrsPerKey) + userInterfaceMatrix, err := security.ListToMatrix(u.IdxInterface, u.Kp.AttrsPerKey) if err != nil { return err } - err = util.FisherYatesShuffle[[]int](&userInterfaceMatrix) + err = security.FisherYatesShuffle[[]int](&userInterfaceMatrix) if err != nil { return err } - u.IdxInterface = util.MatrixToList(userInterfaceMatrix) + u.IdxInterface = security.MatrixToList(userInterfaceMatrix) return nil } @@ -101,9 +102,9 @@ func (u *UserInterface) randomAttributeRotation() error { return err } - transposeUserInterface, err := util.MatrixTranspose(userInterface) + transposeUserInterface, err := security.MatrixTranspose(userInterface) - attrRotation, err := util.RandomPermutation(len(transposeUserInterface)) + attrRotation, err := security.RandomPermutation(len(transposeUserInterface)) if err != nil { return err @@ -112,23 +113,23 @@ func (u *UserInterface) randomAttributeRotation() error { rotation := attrRotation[idx] transposeUserInterface[idx] = append(attrSet[rotation:], attrSet[:rotation]...) } - userInterface, err = util.MatrixTranspose(transposeUserInterface) + userInterface, err = security.MatrixTranspose(transposeUserInterface) if err != nil { return err } - u.IdxInterface = util.MatrixToList(userInterface) + u.IdxInterface = security.MatrixToList(userInterface) return nil } -func (u *UserInterface) AttributeAdjacencyGraph() (map[int]hashset.Set[int], error) { +func (u *UserInterface) AttributeAdjacencyGraph() (map[int]utils.Set[int], error) { interfaceKeypad, err := u.InterfaceMatrix() if err != nil { return nil, err } - graph := make(map[int]hashset.Set[int]) + graph := make(map[int]utils.Set[int]) for _, key := range interfaceKeypad { - keySet := hashset.NewSetFromSlice(key) + keySet := utils.NewSetFromSlice(key) for _, attr := range key { attrAdjacency := keySet.Copy() attrAdjacency.Remove(attr) @@ -145,13 +146,13 @@ func (u *UserInterface) PartialInterfaceShuffle() error { } numbOfSelectedSets := u.Kp.AttrsPerKey / 2 if u.Kp.AttrsPerKey&1 == 1 { - numbOfSelectedSets += util.Choice[int]([]int{0, 1}) + numbOfSelectedSets += security.Choice[int]([]int{0, 1}) } - setIdxs, err := util.RandomPermutation(u.Kp.AttrsPerKey) + setIdxs, err := security.RandomPermutation(u.Kp.AttrsPerKey) if err != nil { return err } - selectedSets := hashset.NewSetFromSlice[int](setIdxs[:numbOfSelectedSets]) + selectedSets := utils.NewSetFromSlice[int](setIdxs[:numbOfSelectedSets]) keypadSetView, err := u.SetViewMatrix() if err != nil { @@ -160,30 +161,30 @@ func (u *UserInterface) PartialInterfaceShuffle() error { interfaceBySet := make([][]int, u.Kp.AttrsPerKey) for idx, attrs := range keypadSetView { if selectedSets.Contains(idx) { - err = util.FisherYatesShuffle[int](&attrs) + err = security.FisherYatesShuffle[int](&attrs) if err != nil { return err } } interfaceBySet[idx] = attrs } - keypadView, err := util.MatrixTranspose(interfaceBySet) + keypadView, err := security.MatrixTranspose(interfaceBySet) if err != nil { return err } - u.IdxInterface = util.MatrixToList(keypadView) + u.IdxInterface = security.MatrixToList(keypadView) return nil } func (u *UserInterface) GetAttrIdxByKeyNumbSetIdx(setIdx int, keyNumb int) (int, error) { if keyNumb < 0 || u.Kp.NumbOfKeys <= keyNumb { log.Printf("keyNumb %d is out of range 0-%d", keyNumb, u.Kp.NumbOfKeys) - return -1, ErrKeyIndexOutOfRange + return -1, config.ErrKeyIndexOutOfRange } if setIdx < 0 || u.Kp.AttrsPerKey <= setIdx { log.Printf("setIdx %d is out of range 0-%d", setIdx, u.Kp.AttrsPerKey) - return -1, ErrAttributeIndexOutOfRange + return -1, config.ErrAttributeIndexOutOfRange } keypadView, err := u.InterfaceMatrix() if err != nil { diff --git a/core/user_signup_session.go b/internal/models/user_signup_session.go similarity index 82% rename from core/user_signup_session.go rename to internal/models/user_signup_session.go index c04b9aa..26692f8 100644 --- a/core/user_signup_session.go +++ b/internal/models/user_signup_session.go @@ -1,10 +1,10 @@ -package core +package models import ( "github.com/google/uuid" - "go-nkode/hashset" - py "go-nkode/py-builtin" - "go-nkode/util" + "go-nkode/config" + "go-nkode/internal/security" + py "go-nkode/internal/utils" "log" "sort" ) @@ -55,32 +55,32 @@ func (s *UserSignSession) DeducePasscode(confirmKeyEntry KeySelection) ([]int, e if !validEntry { log.Printf("Invalid Key entry. One or more key index: %#v, not in range 0-%d", confirmKeyEntry, s.Kp.NumbOfKeys) - return nil, ErrKeyIndexOutOfRange + return nil, config.ErrKeyIndexOutOfRange } if s.SetIdxInterface == nil { log.Print("signup session set interface is nil") - return nil, ErrIncompleteUserSignupSession + return nil, config.ErrIncompleteUserSignupSession } if s.ConfirmIdxInterface == nil { log.Print("signup session confirm interface is nil") - return nil, ErrIncompleteUserSignupSession + return nil, config.ErrIncompleteUserSignupSession } if s.SetKeySelection == nil { log.Print("signup session set key entry is nil") - return nil, ErrIncompleteUserSignupSession + return nil, config.ErrIncompleteUserSignupSession } if s.UserEmail == "" { log.Print("signup session username is nil") - return nil, ErrIncompleteUserSignupSession + return nil, config.ErrIncompleteUserSignupSession } if len(confirmKeyEntry) != len(s.SetKeySelection) { log.Printf("confirm and set key entry length mismatch %d != %d", len(confirmKeyEntry), len(s.SetKeySelection)) - return nil, ErrSetConfirmSignupMismatch + return nil, config.ErrSetConfirmSignupMismatch } passcodeLen := len(confirmKeyEntry) @@ -92,16 +92,16 @@ func (s *UserSignSession) DeducePasscode(confirmKeyEntry KeySelection) ([]int, e passcode := make([]int, passcodeLen) for idx := 0; idx < passcodeLen; idx++ { - setKey := hashset.NewSetFromSlice[int](setKeyVals[idx]) - confirmKey := hashset.NewSetFromSlice[int](confirmKeyVals[idx]) + setKey := py.NewSetFromSlice[int](setKeyVals[idx]) + confirmKey := py.NewSetFromSlice[int](confirmKeyVals[idx]) intersection := setKey.Intersect(confirmKey) if intersection.Size() < 1 { log.Printf("set and confirm do not intersect at index %d", idx) - return nil, ErrSetConfirmSignupMismatch + return nil, config.ErrSetConfirmSignupMismatch } if intersection.Size() > 1 { log.Printf("set and confirm intersect at more than one point at index %d", idx) - return nil, ErrSetConfirmSignupMismatch + return nil, config.ErrSetConfirmSignupMismatch } intersectionSlice := intersection.ToSlice() passcode[idx] = intersectionSlice[0] @@ -115,7 +115,7 @@ func (s *UserSignSession) SetUserNKode(keySelection KeySelection) (IdxInterface, }) if !validKeySelection { log.Printf("one or key selection is out of range 0-%d", s.Kp.NumbOfKeys-1) - return nil, ErrKeyIndexOutOfRange + return nil, config.ErrKeyIndexOutOfRange } s.SetKeySelection = keySelection @@ -131,7 +131,7 @@ func (s *UserSignSession) SetUserNKode(keySelection KeySelection) (IdxInterface, func (s *UserSignSession) getSelectedKeyVals(keySelections KeySelection, userInterface []int) ([][]int, error) { signupKp := s.SignupKeypad() - keypadInterface, err := util.ListToMatrix(userInterface, signupKp.AttrsPerKey) + keypadInterface, err := security.ListToMatrix(userInterface, signupKp.AttrsPerKey) if err != nil { return nil, err } @@ -146,7 +146,7 @@ func (s *UserSignSession) getSelectedKeyVals(keySelections KeySelection, userInt func signupInterface(baseUserInterface UserInterface, kp KeypadDimension) (*UserInterface, []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, ErrKeypadIsNotDispersible + return nil, nil, config.ErrKeypadIsNotDispersible } err := baseUserInterface.RandomShuffle() if err != nil { @@ -158,13 +158,13 @@ func signupInterface(baseUserInterface UserInterface, kp KeypadDimension) (*User return nil, nil, err } // attributes are arranged by set - attrSetView, err := util.MatrixTranspose(interfaceMatrix) + attrSetView, err := security.MatrixTranspose(interfaceMatrix) if err != nil { return nil, nil, err } - setIdxs := util.IdentityArray(kp.AttrsPerKey) - if err := util.FisherYatesShuffle[int](&setIdxs); err != nil { + setIdxs := security.IdentityArray(kp.AttrsPerKey) + if err := security.FisherYatesShuffle[int](&setIdxs); err != nil { return nil, nil, err } setIdxs = setIdxs[:kp.NumbOfKeys] @@ -177,13 +177,13 @@ func signupInterface(baseUserInterface UserInterface, kp KeypadDimension) (*User selectedColors[idx] = SetColors[setIdx] } // convert set view back into key view - selectedSets, err = util.MatrixTranspose(selectedSets) + selectedSets, err = security.MatrixTranspose(selectedSets) if err != nil { return nil, nil, err } signupUserInterface := UserInterface{ - IdxInterface: util.MatrixToList(selectedSets), + IdxInterface: security.MatrixToList(selectedSets), Kp: &KeypadDimension{ AttrsPerKey: kp.NumbOfKeys, NumbOfKeys: kp.NumbOfKeys, diff --git a/core/user_test.go b/internal/models/user_test.go similarity index 98% rename from core/user_test.go rename to internal/models/user_test.go index b7b5e09..3202a0a 100644 --- a/core/user_test.go +++ b/internal/models/user_test.go @@ -1,8 +1,8 @@ -package core +package models import ( "github.com/stretchr/testify/assert" - py "go-nkode/py-builtin" + py "go-nkode/internal/utils" "testing" ) diff --git a/core/jwt_claims.go b/internal/security/jwt_claims.go similarity index 79% rename from core/jwt_claims.go rename to internal/security/jwt_claims.go index dc43d2e..2d01aae 100644 --- a/core/jwt_claims.go +++ b/internal/security/jwt_claims.go @@ -1,8 +1,9 @@ -package core +package security import ( "github.com/golang-jwt/jwt/v5" - "go-nkode/util" + "github.com/google/uuid" + "go-nkode/config" "log" "os" "time" @@ -32,19 +33,19 @@ func getJwtSecret() []byte { log.Fatal("No JWT_SECRET found") } - jwtBytes, err := util.ParseHexString(jwtSecret) + jwtBytes, err := ParseHexString(jwtSecret) if err != nil { log.Fatalf("error parsing jwt secret %v", err) } return jwtBytes } -func NewAuthenticationTokens(username string, customerId CustomerId) (AuthenticationTokens, error) { +func NewAuthenticationTokens(username string, customerId uuid.UUID) (AuthenticationTokens, error) { accessClaims := NewAccessClaim(username, customerId) refreshClaims := jwt.RegisteredClaims{ Subject: username, - Issuer: CustomerIdToString(customerId), + Issuer: customerId.String(), ExpiresAt: jwt.NewNumericDate(time.Now().Add(refreshTokenExp)), } @@ -63,10 +64,10 @@ func NewAuthenticationTokens(username string, customerId CustomerId) (Authentica }, nil } -func NewAccessClaim(username string, customerId CustomerId) jwt.RegisteredClaims { +func NewAccessClaim(username string, customerId uuid.UUID) jwt.RegisteredClaims { return jwt.RegisteredClaims{ Subject: username, - Issuer: CustomerIdToString(customerId), + Issuer: customerId.String(), ExpiresAt: jwt.NewNumericDate(time.Now().Add(accessTokenExp)), } } @@ -90,31 +91,31 @@ func parseJwt[T *ResetNKodeClaims | *jwt.RegisteredClaims](tokenStr string, clai }) if err != nil { log.Printf("error parsing refresh token: %v", err) - return nil, ErrInvalidJwt + return nil, config.ErrInvalidJwt } claims, ok := token.Claims.(T) if !ok { - return nil, ErrInvalidJwt + return nil, config.ErrInvalidJwt } return claims, nil } func ClaimExpired(claims jwt.RegisteredClaims) error { if claims.ExpiresAt == nil { - return ErrClaimExpOrNil + return config.ErrClaimExpOrNil } if claims.ExpiresAt.Time.After(time.Now()) { return nil } - return ErrClaimExpOrNil + return config.ErrClaimExpOrNil } -func ResetNKodeToken(userEmail UserEmail, customerId CustomerId) (string, error) { +func ResetNKodeToken(userEmail string, customerId uuid.UUID) (string, error) { resetClaims := ResetNKodeClaims{ true, jwt.RegisteredClaims{ - Subject: string(userEmail), - Issuer: CustomerIdToString(customerId), + Subject: userEmail, + Issuer: customerId.String(), ExpiresAt: jwt.NewNumericDate(time.Now().Add(resetNKodeTokenExp)), }, } diff --git a/core/jwt_claims_test.go b/internal/security/jwt_claims_test.go similarity index 87% rename from core/jwt_claims_test.go rename to internal/security/jwt_claims_test.go index e9aec63..e2c1b50 100644 --- a/core/jwt_claims_test.go +++ b/internal/security/jwt_claims_test.go @@ -1,4 +1,4 @@ -package core +package security import ( "github.com/google/uuid" @@ -8,7 +8,7 @@ import ( func TestJwtClaims(t *testing.T) { email := "testing@example.com" - customerId := CustomerId(uuid.New()) + customerId := uuid.New() authTokens, err := NewAuthenticationTokens(email, customerId) assert.NoError(t, err) accessToken, err := ParseRegisteredClaimToken(authTokens.AccessToken) @@ -19,7 +19,7 @@ func TestJwtClaims(t *testing.T) { assert.NoError(t, err) assert.Equal(t, refreshToken.Subject, email) assert.NoError(t, ClaimExpired(*refreshToken)) - resetNKode, err := ResetNKodeToken(UserEmail(email), customerId) + resetNKode, err := ResetNKodeToken(email, customerId) assert.NoError(t, err) resetToken, err := ParseRestNKodeToken(resetNKode) assert.NoError(t, err) diff --git a/util/util.go b/internal/security/util.go similarity index 98% rename from util/util.go rename to internal/security/util.go index 59b5ecc..f40193a 100644 --- a/util/util.go +++ b/internal/security/util.go @@ -1,4 +1,4 @@ -package util +package security import ( "crypto/rand" @@ -6,7 +6,7 @@ import ( "encoding/binary" "encoding/hex" "errors" - "go-nkode/hashset" + "go-nkode/internal/utils" "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(hashset.Set[uint64]) + listSet := make(utils.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(hashset.Set[int]) + listSet := make(utils.Set[int]) for { if listSet.Size() == listLen { break diff --git a/util/util_test.go b/internal/security/util_test.go similarity index 98% rename from util/util_test.go rename to internal/security/util_test.go index 7c49f21..78b8c9d 100644 --- a/util/util_test.go +++ b/internal/security/util_test.go @@ -1,4 +1,4 @@ -package util +package security import ( "github.com/stretchr/testify/assert" diff --git a/hashset/hashset.go b/internal/utils/hashset.go similarity index 98% rename from hashset/hashset.go rename to internal/utils/hashset.go index 5788146..dc5a9aa 100644 --- a/hashset/hashset.go +++ b/internal/utils/hashset.go @@ -1,4 +1,4 @@ -package hashset +package utils type Set[T comparable] map[T]struct{} diff --git a/hashset/hashset_test.go b/internal/utils/hashset_test.go similarity index 97% rename from hashset/hashset_test.go rename to internal/utils/hashset_test.go index 7912f84..85b9c40 100644 --- a/hashset/hashset_test.go +++ b/internal/utils/hashset_test.go @@ -1,4 +1,4 @@ -package hashset +package utils import ( "github.com/stretchr/testify/assert" diff --git a/py-builtin/py-builtin.go b/internal/utils/py-builtin.go similarity index 88% rename from py-builtin/py-builtin.go rename to internal/utils/py-builtin.go index 3c2cac9..b613598 100644 --- a/py-builtin/py-builtin.go +++ b/internal/utils/py-builtin.go @@ -1,4 +1,4 @@ -package py_builtin +package utils func All[T comparable](slice []T, condition func(T) bool) bool { diff --git a/main.go b/main.go deleted file mode 100644 index 4299b89..0000000 --- a/main.go +++ /dev/null @@ -1,77 +0,0 @@ -package main - -import ( - "fmt" - "github.com/google/uuid" - "go-nkode/core" - "log" - "net/http" - "os" -) - -const ( - emailQueueBufferSize = 100 - maxEmailsPerSecond = 13 // SES allows 14, but I don't want to push it -) - -func main() { - dbPath := os.Getenv("SQLITE_DB") - if dbPath == "" { - log.Fatalf("SQLITE_DB=/path/to/nkode.db not set") - } - db := core.NewSqliteDB(dbPath) - defer db.CloseDb() - sesClient := core.NewSESClient() - emailQueue := core.NewEmailQueue(emailQueueBufferSize, maxEmailsPerSecond, &sesClient) - emailQueue.Start() - defer emailQueue.Stop() - nkodeApi := core.NewNKodeAPI(db, emailQueue) - AddDefaultCustomer(nkodeApi) - handler := core.NKodeHandler{Api: nkodeApi} - mux := http.NewServeMux() - mux.Handle(core.CreateNewCustomer, &handler) - mux.Handle(core.GenerateSignupResetInterface, &handler) - mux.Handle(core.SetNKode, &handler) - mux.Handle(core.ConfirmNKode, &handler) - mux.Handle(core.GetLoginInterface, &handler) - mux.Handle(core.Login, &handler) - mux.Handle(core.RenewAttributes, &handler) - mux.Handle(core.RandomSvgInterface, &handler) - mux.Handle(core.RefreshToken, &handler) - mux.Handle(core.ResetNKode, &handler) - fmt.Println("Running on localhost:8080...") - log.Fatal(http.ListenAndServe(":8080", corsMiddleware(mux))) -} - -func corsMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Set the CORS headers - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, DELETE") - w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization") - - // Handle preflight requests - if r.Method == http.MethodOptions { - w.WriteHeader(http.StatusNoContent) - return - } - - // Call the next handler - next.ServeHTTP(w, r) - }) -} - -func AddDefaultCustomer(api core.NKodeAPI) { - newId, err := uuid.Parse("ed9ed6e0-082c-4b57-8d8c-f00ed6493457") - if err != nil { - log.Fatal(err) - } - customerId := core.CustomerId(newId) - nkodePolicy := core.NewDefaultNKodePolicy() - _, err = api.CreateNewCustomer(nkodePolicy, &customerId) - if err != nil { - log.Println(err) - } else { - log.Println("created new customer: ", newId) - } -} diff --git a/script/backup_sqlite.sh b/scripts/backup_sqlite.sh similarity index 100% rename from script/backup_sqlite.sh rename to scripts/backup_sqlite.sh diff --git a/script/docker_build.sh b/scripts/docker_build.sh similarity index 100% rename from script/docker_build.sh rename to scripts/docker_build.sh -- 2.49.1 From ca17c0645da1542871cbb9cf1173719be6ecd2d4 Mon Sep 17 00:00:00 2001 From: Donovan Date: Tue, 26 Nov 2024 11:32:20 -0600 Subject: [PATCH 21/41] basic swagger --- docs/docs.go | 129 ++++++++++++++++++++++++++++++++++++++++++++++ docs/swagger.json | 105 +++++++++++++++++++++++++++++++++++++ docs/swagger.yaml | 70 +++++++++++++++++++++++++ 3 files changed, 304 insertions(+) create mode 100644 docs/docs.go create mode 100644 docs/swagger.json create mode 100644 docs/swagger.yaml diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..9c14e73 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,129 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "termsOfService": "http://nkode.example.com/terms/", + "contact": { + "name": "API Support", + "url": "http://nkode.example.com/support", + "email": "support@nkode.example.com" + }, + "license": { + "name": "MIT", + "url": "https://opensource.org/licenses/MIT" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/create-new-customer": { + "post": { + "description": "Creates a new customer based on the provided policy information.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "customers" + ], + "summary": "Create a new customer", + "parameters": [ + { + "description": "Customer creation data", + "name": "NewCustomerPost", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/core.NewCustomerPost" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/core.CreateNewCustomerResp" + } + } + } + } + } + }, + "definitions": { + "core.CreateNewCustomerResp": { + "type": "object", + "properties": { + "customer_id": { + "type": "string" + } + } + }, + "core.NKodePolicy": { + "type": "object", + "properties": { + "distinct_attributes": { + "type": "integer" + }, + "distinct_sets": { + "type": "integer" + }, + "expiration": { + "description": "seconds, -1 no expiration", + "type": "integer" + }, + "lock_out": { + "type": "integer" + }, + "max_nkode_len": { + "type": "integer" + }, + "min_nkode_len": { + "type": "integer" + } + } + }, + "core.NewCustomerPost": { + "type": "object", + "properties": { + "nkode_policy": { + "$ref": "#/definitions/core.NKodePolicy" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "1.0", + Host: "localhost:8080", + BasePath: "/", + Schemes: []string{}, + Title: "NKode API", + Description: "This is the NKode API server.", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..f20661e --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,105 @@ +{ + "swagger": "2.0", + "info": { + "description": "This is the NKode API server.", + "title": "NKode API", + "termsOfService": "http://nkode.example.com/terms/", + "contact": { + "name": "API Support", + "url": "http://nkode.example.com/support", + "email": "support@nkode.example.com" + }, + "license": { + "name": "MIT", + "url": "https://opensource.org/licenses/MIT" + }, + "version": "1.0" + }, + "host": "localhost:8080", + "basePath": "/", + "paths": { + "/create-new-customer": { + "post": { + "description": "Creates a new customer based on the provided policy information.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "customers" + ], + "summary": "Create a new customer", + "parameters": [ + { + "description": "Customer creation data", + "name": "NewCustomerPost", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/core.NewCustomerPost" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/core.CreateNewCustomerResp" + } + } + } + } + } + }, + "definitions": { + "core.CreateNewCustomerResp": { + "type": "object", + "properties": { + "customer_id": { + "type": "string" + } + } + }, + "core.NKodePolicy": { + "type": "object", + "properties": { + "distinct_attributes": { + "type": "integer" + }, + "distinct_sets": { + "type": "integer" + }, + "expiration": { + "description": "seconds, -1 no expiration", + "type": "integer" + }, + "lock_out": { + "type": "integer" + }, + "max_nkode_len": { + "type": "integer" + }, + "min_nkode_len": { + "type": "integer" + } + } + }, + "core.NewCustomerPost": { + "type": "object", + "properties": { + "nkode_policy": { + "$ref": "#/definitions/core.NKodePolicy" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..fb0b9dd --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,70 @@ +basePath: / +definitions: + core.CreateNewCustomerResp: + properties: + customer_id: + type: string + type: object + core.NKodePolicy: + properties: + distinct_attributes: + type: integer + distinct_sets: + type: integer + expiration: + description: seconds, -1 no expiration + type: integer + lock_out: + type: integer + max_nkode_len: + type: integer + min_nkode_len: + type: integer + type: object + core.NewCustomerPost: + properties: + nkode_policy: + $ref: '#/definitions/core.NKodePolicy' + type: object +host: localhost:8080 +info: + contact: + email: support@nkode.example.com + name: API Support + url: http://nkode.example.com/support + description: This is the NKode API server. + license: + name: MIT + url: https://opensource.org/licenses/MIT + termsOfService: http://nkode.example.com/terms/ + title: NKode API + version: "1.0" +paths: + /create-new-customer: + post: + consumes: + - application/json + description: Creates a new customer based on the provided policy information. + parameters: + - description: Customer creation data + in: body + name: NewCustomerPost + required: true + schema: + $ref: '#/definitions/core.NewCustomerPost' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/core.CreateNewCustomerResp' + summary: Create a new customer + tags: + - customers +securityDefinitions: + ApiKeyAuth: + in: header + name: Authorization + type: apiKey +swagger: "2.0" -- 2.49.1 From c0b785ca8ddd5c2571f9f2e98ce61d0d98d87899 Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 27 Nov 2024 09:41:31 -0600 Subject: [PATCH 22/41] more refactoring --- .env.test.example | 1 + cmd/main_test.go | 15 +++---- internal/api/db_interface.go | 20 ---------- internal/api/handler.go | 3 +- internal/api/nkode_api.go | 24 ++++++------ internal/api/nkode_api_test.go | 29 +++++++------- internal/db/customer_user_repository.go | 21 ++++++++++ internal/db/in_memory_db.go | 27 ++++++------- internal/db/sqlite_db.go | 39 ++++++++++--------- internal/db/sqlite_db_test.go | 16 ++++---- internal/email/queue.go | 20 +++++----- internal/{models => entities}/customer.go | 11 +++--- .../customer_attributes.go | 2 +- .../{models => entities}/customer_test.go | 11 +++--- .../{models => entities}/keypad_dimension.go | 2 +- internal/{models => entities}/test_helper.go | 2 +- internal/{models => entities}/user.go | 17 ++++---- .../{models => entities}/user_cipher_keys.go | 7 ++-- .../{models => entities}/user_interface.go | 9 +++-- .../user_signup_session.go | 33 ++++++++-------- internal/{models => entities}/user_test.go | 9 +++-- 21 files changed, 167 insertions(+), 151 deletions(-) create mode 100644 .env.test.example delete mode 100644 internal/api/db_interface.go create mode 100644 internal/db/customer_user_repository.go rename internal/{models => entities}/customer.go (89%) rename internal/{models => entities}/customer_attributes.go (99%) rename internal/{models => entities}/customer_test.go (86%) rename internal/{models => entities}/keypad_dimension.go (98%) rename internal/{models => entities}/test_helper.go (97%) rename internal/{models => entities}/user.go (92%) rename internal/{models => entities}/user_cipher_keys.go (97%) rename internal/{models => entities}/user_interface.go (95%) rename internal/{models => entities}/user_signup_session.go (83%) rename internal/{models => entities}/user_test.go (94%) diff --git a/.env.test.example b/.env.test.example new file mode 100644 index 0000000..c8b012b --- /dev/null +++ b/.env.test.example @@ -0,0 +1 @@ +TEST_DB=/path/to/test.db \ No newline at end of file diff --git a/cmd/main_test.go b/cmd/main_test.go index 0834b66..e20f4c3 100644 --- a/cmd/main_test.go +++ b/cmd/main_test.go @@ -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 diff --git a/internal/api/db_interface.go b/internal/api/db_interface.go deleted file mode 100644 index 945b5a4..0000000 --- a/internal/api/db_interface.go +++ /dev/null @@ -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) -} diff --git a/internal/api/handler.go b/internal/api/handler.go index cbcea0d..f7dac38 100644 --- a/internal/api/handler.go +++ b/internal/api/handler.go @@ -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, } diff --git a/internal/api/nkode_api.go b/internal/api/nkode_api.go index 69fe69a..eeaf9c5 100644 --- a/internal/api/nkode_api.go +++ b/internal/api/nkode_api.go @@ -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,7 +35,7 @@ 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 } @@ -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) { diff --git a/internal/api/nkode_api_test.go b/internal/api/nkode_api_test.go index 80d1b1a..ebc217f 100644 --- a/internal/api/nkode_api_test.go +++ b/internal/api/nkode_api_test.go @@ -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" @@ -28,7 +29,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 +42,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 +52,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 +75,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) diff --git a/internal/db/customer_user_repository.go b/internal/db/customer_user_repository.go new file mode 100644 index 0000000..4cccc0b --- /dev/null +++ b/internal/db/customer_user_repository.go @@ -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) + WriteNewCustomer(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) +} diff --git a/internal/db/in_memory_db.go b/internal/db/in_memory_db.go index 2e3197e..05405fb 100644 --- a/internal/db/in_memory_db.go +++ b/internal/db/in_memory_db.go @@ -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) WriteNewCustomer(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 diff --git a/internal/db/sqlite_db.go b/internal/db/sqlite_db.go index a236780..827995d 100644 --- a/internal/db/sqlite_db.go +++ b/internal/db/sqlite_db.go @@ -6,6 +6,7 @@ import ( "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" "log" @@ -60,7 +61,7 @@ func (d *SqliteDB) CloseDb() { } } -func (d *SqliteDB) WriteNewCustomer(c models.Customer) error { +func (d *SqliteDB) WriteNewCustomer(c entities.Customer) error { query := ` INSERT INTO customer ( id @@ -85,7 +86,7 @@ VALUES (?,?,?,?,?,?,?,?,?,?,?) return d.addWriteTx(query, args) } -func (d *SqliteDB) WriteNewUser(u models.User) error { +func (d *SqliteDB) WriteNewUser(u entities.User) error { query := ` INSERT INTO user ( id @@ -128,7 +129,7 @@ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) return d.addWriteTx(query, args) } -func (d *SqliteDB) UpdateUserNKode(u models.User) error { +func (d *SqliteDB) UpdateUserNKode(u entities.User) error { query := ` UPDATE user SET renew = ? @@ -158,7 +159,7 @@ WHERE email = ? AND customer_id = ? return d.addWriteTx(query, args) } -func (d *SqliteDB) UpdateUserInterface(id models.UserId, ui models.UserInterface) error { +func (d *SqliteDB) UpdateUserInterface(id models.UserId, ui entities.UserInterface) error { query := ` UPDATE user SET idx_interface = ?, last_login = ? WHERE id = ? ` @@ -219,20 +220,20 @@ WHERE customer_id = ? if err != nil { return err } - user := models.User{ + user := entities.User{ Id: models.UserId{}, CustomerId: models.CustomerId{}, Email: "", EncipheredPasscode: models.EncipheredNKode{}, - Kp: models.KeypadDimension{ + Kp: entities.KeypadDimension{ AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys, }, - CipherKeys: models.UserCipherKeys{ + CipherKeys: entities.UserCipherKeys{ AlphaKey: security.ByteArrToUint64Arr(alphaBytes), SetKey: security.ByteArrToUint64Arr(setBytes), }, - Interface: models.UserInterface{}, + Interface: entities.UserInterface{}, Renew: false, } err = user.RenewKeys(setXor, attrXor) @@ -255,7 +256,7 @@ WHERE id = ?; return d.addWriteTx(renewQuery, renewArgs) } -func (d *SqliteDB) RefreshUserPasscode(user models.User, passcodeIdx []int, customerAttr models.CustomerAttributes) error { +func (d *SqliteDB) RefreshUserPasscode(user entities.User, passcodeIdx []int, customerAttr entities.CustomerAttributes) error { err := user.RefreshPasscode(passcodeIdx, customerAttr) if err != nil { return err @@ -276,7 +277,7 @@ WHERE id = ?; args := []any{user.RefreshToken, 0, user.EncipheredPasscode.Code, user.EncipheredPasscode.Mask, security.Uint64ArrToByteArr(user.CipherKeys.AlphaKey), security.Uint64ArrToByteArr(user.CipherKeys.SetKey), security.Uint64ArrToByteArr(user.CipherKeys.PassKey), security.Uint64ArrToByteArr(user.CipherKeys.MaskKey), user.CipherKeys.Salt, uuid.UUID(user.Id).String()} return d.addWriteTx(query, args) } -func (d *SqliteDB) GetCustomer(id models.CustomerId) (*models.Customer, error) { +func (d *SqliteDB) GetCustomer(id models.CustomerId) (*entities.Customer, error) { tx, err := d.db.Begin() if err != nil { return nil, err @@ -324,7 +325,7 @@ WHERE id = ? if err != nil { return nil, err } - customer := models.Customer{ + customer := entities.Customer{ Id: id, NKodePolicy: models.NKodePolicy{ MaxNkodeLen: maxNKodeLen, @@ -334,7 +335,7 @@ WHERE id = ? LockOut: lockOut, Expiration: expiration, }, - Attributes: models.NewCustomerAttributesFromBytes(attributeValues, setValues), + Attributes: entities.NewCustomerAttributesFromBytes(attributeValues, setValues), } if err = tx.Commit(); err != nil { return nil, err @@ -342,7 +343,7 @@ WHERE id = ? return &customer, nil } -func (d *SqliteDB) GetUser(email models.UserEmail, customerId models.CustomerId) (*models.User, error) { +func (d *SqliteDB) GetUser(email models.UserEmail, customerId models.CustomerId) (*entities.User, error) { tx, err := d.db.Begin() if err != nil { return nil, err @@ -401,7 +402,7 @@ WHERE user.email = ? AND user.customer_id = ? renew = true } - user := models.User{ + user := entities.User{ Id: models.UserId(userId), CustomerId: customerId, Email: email, @@ -409,11 +410,11 @@ WHERE user.email = ? AND user.customer_id = ? Code: code, Mask: mask, }, - Kp: models.KeypadDimension{ + Kp: entities.KeypadDimension{ AttrsPerKey: attrsPerKey, NumbOfKeys: numbOfKeys, }, - CipherKeys: models.UserCipherKeys{ + CipherKeys: entities.UserCipherKeys{ AlphaKey: security.ByteArrToUint64Arr(alphaKey), SetKey: security.ByteArrToUint64Arr(setKey), PassKey: security.ByteArrToUint64Arr(passKey), @@ -422,7 +423,7 @@ WHERE user.email = ? AND user.customer_id = ? MaxNKodeLen: maxNKodeLen, Kp: nil, }, - Interface: models.UserInterface{ + Interface: entities.UserInterface{ IdxInterface: security.ByteArrToIntArr(idxInterface), SvgId: security.ByteArrToIntArr(svgIdInterface), Kp: nil, @@ -438,7 +439,7 @@ WHERE user.email = ? AND user.customer_id = ? 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 +447,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()) } diff --git a/internal/db/sqlite_db_test.go b/internal/db/sqlite_db_test.go index 82a5cac..9f53ef1 100644 --- a/internal/db/sqlite_db_test.go +++ b/internal/db/sqlite_db_test.go @@ -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" @@ -24,9 +24,9 @@ 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) assert.NoError(t, err) @@ -34,12 +34,12 @@ func testSignupLoginRenew(t *testing.T, db api.DbAccessor) { 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 +52,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()) diff --git a/internal/email/queue.go b/internal/email/queue.go index 3480157..597694b 100644 --- a/internal/email/queue.go +++ b/internal/email/queue.go @@ -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() diff --git a/internal/models/customer.go b/internal/entities/customer.go similarity index 89% rename from internal/models/customer.go rename to internal/entities/customer.go index 3183368..a111cc4 100644 --- a/internal/models/customer.go +++ b/internal/entities/customer.go @@ -1,25 +1,26 @@ -package models +package entities import ( "github.com/google/uuid" "go-nkode/config" + "go-nkode/internal/models" "go-nkode/internal/security" "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, } diff --git a/internal/models/customer_attributes.go b/internal/entities/customer_attributes.go similarity index 99% rename from internal/models/customer_attributes.go rename to internal/entities/customer_attributes.go index 64827d9..4d3eb5d 100644 --- a/internal/models/customer_attributes.go +++ b/internal/entities/customer_attributes.go @@ -1,4 +1,4 @@ -package models +package entities import ( "go-nkode/internal/security" diff --git a/internal/models/customer_test.go b/internal/entities/customer_test.go similarity index 86% rename from internal/models/customer_test.go rename to internal/entities/customer_test.go index 1b15fdb..0e98034 100644 --- a/internal/models/customer_test.go +++ b/internal/entities/customer_test.go @@ -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" diff --git a/internal/models/keypad_dimension.go b/internal/entities/keypad_dimension.go similarity index 98% rename from internal/models/keypad_dimension.go rename to internal/entities/keypad_dimension.go index 75ccfed..3ffd8e4 100644 --- a/internal/models/keypad_dimension.go +++ b/internal/entities/keypad_dimension.go @@ -1,4 +1,4 @@ -package models +package entities import ( "go-nkode/config" diff --git a/internal/models/test_helper.go b/internal/entities/test_helper.go similarity index 97% rename from internal/models/test_helper.go rename to internal/entities/test_helper.go index 33cea57..2874516 100644 --- a/internal/models/test_helper.go +++ b/internal/entities/test_helper.go @@ -1,4 +1,4 @@ -package models +package entities import ( "errors" diff --git a/internal/models/user.go b/internal/entities/user.go similarity index 92% rename from internal/models/user.go rename to internal/entities/user.go index 86847e3..ec89efe 100644 --- a/internal/models/user.go +++ b/internal/entities/user.go @@ -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 @@ -116,7 +117,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 +134,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, diff --git a/internal/models/user_cipher_keys.go b/internal/entities/user_cipher_keys.go similarity index 97% rename from internal/models/user_cipher_keys.go rename to internal/entities/user_cipher_keys.go index 7f187a9..8168c61 100644 --- a/internal/models/user_cipher_keys.go +++ b/internal/entities/user_cipher_keys.go @@ -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, } diff --git a/internal/models/user_interface.go b/internal/entities/user_interface.go similarity index 95% rename from internal/models/user_interface.go rename to internal/entities/user_interface.go index d5ba0a3..0a691e8 100644 --- a/internal/models/user_interface.go +++ b/internal/entities/user_interface.go @@ -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, diff --git a/internal/models/user_signup_session.go b/internal/entities/user_signup_session.go similarity index 83% rename from internal/models/user_signup_session.go rename to internal/entities/user_signup_session.go index 26692f8..de46146 100644 --- a/internal/models/user_signup_session.go +++ b/internal/entities/user_signup_session.go @@ -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) diff --git a/internal/models/user_test.go b/internal/entities/user_test.go similarity index 94% rename from internal/models/user_test.go rename to internal/entities/user_test.go index 3202a0a..9d93c5c 100644 --- a/internal/models/user_test.go +++ b/internal/entities/user_test.go @@ -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 -- 2.49.1 From 69ec9bd08cd613ede603ada72f8a404ddf456f75 Mon Sep 17 00:00:00 2001 From: Donovan Date: Tue, 3 Dec 2024 16:22:03 -0600 Subject: [PATCH 23/41] sqlc generate --- internal/sqlc/db.go | 31 +++ internal/sqlc/models.go | 50 ++++ internal/sqlc/query.sql.go | 478 +++++++++++++++++++++++++++++++++++++ sqlc.yaml | 9 + sqlite/query.sql | 136 +++++++++++ sqlite/schema.sql | 57 +++++ 6 files changed, 761 insertions(+) create mode 100644 internal/sqlc/db.go create mode 100644 internal/sqlc/models.go create mode 100644 internal/sqlc/query.sql.go create mode 100644 sqlc.yaml create mode 100644 sqlite/query.sql create mode 100644 sqlite/schema.sql diff --git a/internal/sqlc/db.go b/internal/sqlc/db.go new file mode 100644 index 0000000..2248616 --- /dev/null +++ b/internal/sqlc/db.go @@ -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, + } +} diff --git a/internal/sqlc/models.go b/internal/sqlc/models.go new file mode 100644 index 0000000..d8f68cf --- /dev/null +++ b/internal/sqlc/models.go @@ -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 +} diff --git a/internal/sqlc/query.sql.go b/internal/sqlc/query.sql.go new file mode 100644 index 0000000..c11071c --- /dev/null +++ b/internal/sqlc/query.sql.go @@ -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 +} diff --git a/sqlc.yaml b/sqlc.yaml new file mode 100644 index 0000000..eda576e --- /dev/null +++ b/sqlc.yaml @@ -0,0 +1,9 @@ +version: "2" +sql: + - engine: "sqlite" + queries: "./sqlite/query.sql" + schema: "./sqlite/schema.sql" + gen: + go: + package: "sqlc" + out: "./internal/sqlc" \ No newline at end of file diff --git a/sqlite/query.sql b/sqlite/query.sql new file mode 100644 index 0000000..b3c50cf --- /dev/null +++ b/sqlite/query.sql @@ -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; diff --git a/sqlite/schema.sql b/sqlite/schema.sql new file mode 100644 index 0000000..4e30249 --- /dev/null +++ b/sqlite/schema.sql @@ -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 +); -- 2.49.1 From bf587792272d10e0829a8bcb0812a5a76c54a2ae Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 4 Dec 2024 10:22:55 -0600 Subject: [PATCH 24/41] refactor sqlite db to support sqlc --- cmd/main.go | 9 +- internal/api/nkode_api.go | 2 +- internal/api/nkode_api_test.go | 5 +- internal/db/customer_user_repository.go | 2 +- internal/db/in_memory_db.go | 2 +- internal/db/sqlite_db.go | 663 +++++++++++------------- internal/db/sqlite_db_test.go | 7 +- internal/email/queue_test.go | 2 +- internal/entities/customer.go | 17 + internal/entities/user.go | 4 +- internal/models/models.go | 10 +- internal/utils/timestamp.go | 7 + 12 files changed, 342 insertions(+), 388 deletions(-) create mode 100644 internal/utils/timestamp.go diff --git a/cmd/main.go b/cmd/main.go index d04297d..534b1d3 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -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 { + fmt.Errorf("%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} diff --git a/internal/api/nkode_api.go b/internal/api/nkode_api.go index eeaf9c5..cf0f581 100644 --- a/internal/api/nkode_api.go +++ b/internal/api/nkode_api.go @@ -42,7 +42,7 @@ func (n *NKodeAPI) CreateNewCustomer(nkodePolicy models.NKodePolicy, id *models. if err != nil { return nil, err } - err = n.Db.WriteNewCustomer(*newCustomer) + err = n.Db.CreateCustomer(*newCustomer) if err != nil { return nil, err diff --git a/internal/api/nkode_api_test.go b/internal/api/nkode_api_test.go index ebc217f..8200d1c 100644 --- a/internal/api/nkode_api_test.go +++ b/internal/api/nkode_api_test.go @@ -17,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 { diff --git a/internal/db/customer_user_repository.go b/internal/db/customer_user_repository.go index 4cccc0b..3b59d51 100644 --- a/internal/db/customer_user_repository.go +++ b/internal/db/customer_user_repository.go @@ -8,7 +8,7 @@ import ( type CustomerUserRepository interface { GetCustomer(models.CustomerId) (*entities.Customer, error) GetUser(models.UserEmail, models.CustomerId) (*entities.User, error) - WriteNewCustomer(entities.Customer) error + CreateCustomer(entities.Customer) error WriteNewUser(entities.User) error UpdateUserNKode(entities.User) error UpdateUserInterface(models.UserId, entities.UserInterface) error diff --git a/internal/db/in_memory_db.go b/internal/db/in_memory_db.go index 05405fb..a73e413 100644 --- a/internal/db/in_memory_db.go +++ b/internal/db/in_memory_db.go @@ -42,7 +42,7 @@ func (db *InMemoryDb) GetUser(username models.UserEmail, customerId models.Custo return &user, nil } -func (db *InMemoryDb) WriteNewCustomer(customer entities.Customer) error { +func (db *InMemoryDb) CreateCustomer(customer entities.Customer) error { _, exists := db.Customers[customer.Id] if exists { diff --git a/internal/db/sqlite_db.go b/internal/db/sqlite_db.go index 827995d..16b48b4 100644 --- a/internal/db/sqlite_db.go +++ b/internal/db/sqlite_db.go @@ -1,7 +1,9 @@ package db import ( + "context" "database/sql" + "errors" "fmt" "github.com/google/uuid" _ "github.com/mattn/go-sqlite3" // Import the SQLite3 driver @@ -9,432 +11,387 @@ import ( "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, 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 } - }() - - return &sqldb + } } -func (d *SqliteDB) CloseDb() { - d.stop = true +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 entities.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 q.CreateCustomer(ctx, params) } - return d.addWriteTx(query, args) + + return d.enqueueWriteTx(queryFunc, c.ToSqlcCreateCustomerParams()) } func (d *SqliteDB) WriteNewUser(u entities.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 + 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 } - - 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(), + // 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.addWriteTx(query, args) + return d.enqueueWriteTx(queryFunc, params) } func (d *SqliteDB) UpdateUserNKode(u entities.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 + 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 q.UpdateUser(ctx, params) + } + // 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 entities.UserInterface) error { - query := ` -UPDATE user SET idx_interface = ?, last_login = ? WHERE id = ? -` - args := []any{security.IntArrToByteArr(ui.IdxInterface), timeStamp(), uuid.UUID(id).String()} + 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 - } - 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 + 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") } + return q.RenewUser(ctx, params) + } + + for _, row := range userRenewRows { user := entities.User{ - Id: models.UserId{}, + Id: models.UserIdFromString(row.ID), CustomerId: models.CustomerId{}, Email: "", EncipheredPasscode: models.EncipheredNKode{}, Kp: entities.KeypadDimension{ - AttrsPerKey: attrsPerKey, - NumbOfKeys: numbOfKeys, + AttrsPerKey: int(row.AttributesPerKey), + NumbOfKeys: int(row.NumberOfKeys), }, CipherKeys: entities.UserCipherKeys{ - AlphaKey: security.ByteArrToUint64Arr(alphaBytes), - SetKey: security.ByteArrToUint64Arr(setBytes), + AlphaKey: security.ByteArrToUint64Arr(row.AlphaKey), + SetKey: security.ByteArrToUint64Arr(row.SetKey), }, Interface: entities.UserInterface{}, Renew: false, } - err = user.RenewKeys(setXor, attrXor) - if err != nil { + + if err = user.RenewKeys(setXor, attrXor); err != nil { + return err + } + params := sqlc.RenewUserParams{ + AlphaKey: security.Uint64ArrToByteArr(user.CipherKeys.AlphaKey), + SetKey: security.Uint64ArrToByteArr(user.CipherKeys.SetKey), + Renew: 1, + ID: uuid.UUID(user.Id).String(), + } + if err = d.enqueueWriteTx(queryFunc, params); 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) } - renewQuery += ` -` - err = tx.Commit() + return nil +} + +func (d *SqliteDB) renewCustomer(id models.CustomerId) ([]uint64, []uint64, error) { + customer, err := d.GetCustomer(id) if err != nil { - return err + return nil, nil, err } - return d.addWriteTx(renewQuery, renewArgs) + 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 { - err := user.RefreshPasscode(passcodeIdx, customerAttr) - if err != nil { + 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) -} -func (d *SqliteDB) GetCustomer(id models.CustomerId) (*entities.Customer, error) { - tx, err := d.db.Begin() - if err != nil { - return nil, err - } - defer func() { - if err != nil { - err = tx.Rollback() - if err != nil { - log.Fatal(fmt.Sprintf("Write new user won't roll back %+v", err)) - } + 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") } - }() - 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)) + return q.RefreshUserPasscode(ctx, params) + } + 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) +} + +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 := entities.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: entities.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) (*entities.User, error) { - tx, err := d.db.Begin() + userRow, err := d.queries.GetUser(d.ctx, sqlc.GetUserParams{ + Email: string(email), + CustomerID: uuid.UUID(customerId).String(), + }) if err != nil { - return nil, err + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + return nil, fmt.Errorf("failed to get user: %w", 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() { - 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 + kp := entities.KeypadDimension{ + AttrsPerKey: int(userRow.AttributesPerKey), + NumbOfKeys: int(userRow.NumberOfKeys), } - var renew bool - if renewVal == 0 { - renew = false - } else { + + renew := false + if userRow.Renew == 1 { renew = true } - user := entities.User{ - Id: models.UserId(userId), + Id: models.UserIdFromString(userRow.ID), CustomerId: customerId, Email: email, EncipheredPasscode: models.EncipheredNKode{ - Code: code, - Mask: mask, - }, - Kp: entities.KeypadDimension{ - AttrsPerKey: attrsPerKey, - NumbOfKeys: numbOfKeys, + Code: userRow.Code, + Mask: userRow.Mask, }, + Kp: kp, CipherKeys: entities.UserCipherKeys{ - AlphaKey: security.ByteArrToUint64Arr(alphaKey), - SetKey: security.ByteArrToUint64Arr(setKey), - PassKey: security.ByteArrToUint64Arr(passKey), - MaskKey: security.ByteArrToUint64Arr(maskKey), - Salt: salt, - MaxNKodeLen: maxNKodeLen, - Kp: nil, + 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, }, Interface: entities.UserInterface{ - IdxInterface: security.ByteArrToIntArr(idxInterface), - SvgId: security.ByteArrToIntArr(svgIdInterface), - Kp: nil, + 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 } @@ -456,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 +func (d *SqliteDB) enqueueWriteTx(queryFunc sqlcGeneric, args any) error { + select { + case <-d.ctx.Done(): + return errors.New("database is shutting down") + default: } - defer func() { - if err != nil { - err = tx.Rollback() - if err != nil { - log.Fatalf("fatal error: write won't roll back %+v", err) - } - } - }() - if _, err = tx.Exec(query, args...); err != nil { - return err - } - if err = tx.Commit(); err != nil { - return err - } - return nil -} -func (d *SqliteDB) addWriteTx(query string, args []any) error { - 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 } @@ -559,7 +478,3 @@ func (d *SqliteDB) getRandomIds(count int) ([]int, error) { return perm[:count], nil } - -func timeStamp() string { - return time.Now().Format(time.RFC3339) -} diff --git a/internal/db/sqlite_db_test.go b/internal/db/sqlite_db_test.go index 9f53ef1..0c6d11e 100644 --- a/internal/db/sqlite_db_test.go +++ b/internal/db/sqlite_db_test.go @@ -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) @@ -28,7 +29,7 @@ func testSignupLoginRenew(t *testing.T, db CustomerUserRepository) { nkodePolicy := models.NewDefaultNKodePolicy() 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) diff --git a/internal/email/queue_test.go b/internal/email/queue_test.go index 525f06a..a0733d0 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) } - // CloseDb the queue after all emails are processed + // Close the queue after all emails are processed queue.Stop() assert.Equal(t, queue.FailedSendCount, 0) diff --git a/internal/entities/customer.go b/internal/entities/customer.go index a111cc4..5ae8fad 100644 --- a/internal/entities/customer.go +++ b/internal/entities/customer.go @@ -5,6 +5,7 @@ import ( "go-nkode/config" "go-nkode/internal/models" "go-nkode/internal/security" + "go-nkode/internal/sqlc" "go-nkode/internal/utils" ) @@ -83,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(), + } +} diff --git a/internal/entities/user.go b/internal/entities/user.go index ec89efe..a4ae96f 100644 --- a/internal/entities/user.go +++ b/internal/entities/user.go @@ -37,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 diff --git a/internal/models/models.go b/internal/models/models.go index 87a8832..49863da 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -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 diff --git a/internal/utils/timestamp.go b/internal/utils/timestamp.go new file mode 100644 index 0000000..4de32dc --- /dev/null +++ b/internal/utils/timestamp.go @@ -0,0 +1,7 @@ +package utils + +import "time" + +func TimeStamp() string { + return time.Now().Format(time.RFC3339) +} -- 2.49.1 From 0c5de93a0da243d33be1dee87c2d018be55b4183 Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 4 Dec 2024 10:39:09 -0600 Subject: [PATCH 25/41] chagne db failed connection to fatal --- cmd/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/main.go b/cmd/main.go index 534b1d3..c119843 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -45,7 +45,7 @@ func main() { } sqlitedb, err := db.NewSqliteDB(dbPath) if err != nil { - fmt.Errorf("%v", err) + log.Fatalf("%v", err) } defer sqlitedb.Close() -- 2.49.1 From a4db8ff84113608b79d615d7d97fbc1c8d3afd7d Mon Sep 17 00:00:00 2001 From: Donovan Date: Wed, 4 Dec 2024 10:54:20 -0600 Subject: [PATCH 26/41] add aws cred args to coolify_compose.yaml --- compose/coolify_compose.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compose/coolify_compose.yaml b/compose/coolify_compose.yaml index 2112488..1dee684 100644 --- a/compose/coolify_compose.yaml +++ b/compose/coolify_compose.yaml @@ -8,4 +8,7 @@ services: environment: - JWT_SECRET=${JWT_SECRET} + - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} + - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} + - AWS_REGION=${AWS_REGION} - FRONTEND_HOST=https://app.nkode.tech -- 2.49.1 From 274d587472d0407015c8e84a4687a7653343b180 Mon Sep 17 00:00:00 2001 From: Donovan Date: Thu, 5 Dec 2024 16:02:04 -0600 Subject: [PATCH 27/41] 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 28/41] 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 29/41] 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 30/41] 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 31/41] 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 32/41] 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 33/41] 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 34/41] 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 35/41] 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 36/41] 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 37/41] 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 38/41] 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 39/41] 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 40/41] 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 41/41] 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