forked from grassrootseconomics/visedriver
Compare commits
20 Commits
lash/purif
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
c0534ede1b | ||
|
24e729d275 | ||
|
ae6e2a99c5 | ||
|
7ca3974371 | ||
|
adbfab3964 | ||
|
5228aef088 | ||
|
5bf0a0e858 | ||
|
f13dab9a45 | ||
|
3f8e08151a | ||
|
9e4c65c8b4 | ||
|
611c5a8dfc | ||
|
dcf777bf08 | ||
|
ec4ad6e44b | ||
|
5b312f9569 | ||
|
2d89db3a37 | ||
|
6d8992fe38 | ||
|
5cffbe5cd8 | ||
|
b28a047777 | ||
|
2ea51d88d8 | ||
|
2e0e854c4b |
@ -1,14 +0,0 @@
|
|||||||
/**
|
|
||||||
!/cmd/africastalking
|
|
||||||
!/cmd/ssh
|
|
||||||
!/common
|
|
||||||
!/config
|
|
||||||
!/initializers
|
|
||||||
!/internal
|
|
||||||
!/models
|
|
||||||
!/remote
|
|
||||||
!/services
|
|
||||||
!/LICENSE
|
|
||||||
!/README.md
|
|
||||||
!/go.*
|
|
||||||
!/.env.example
|
|
56
.github/workflows/docker.yaml
vendored
56
.github/workflows/docker.yaml
vendored
@ -1,56 +0,0 @@
|
|||||||
name: release
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
docker:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- name: Check out repo
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Cache Docker layers
|
|
||||||
uses: actions/cache@v3
|
|
||||||
with:
|
|
||||||
path: /tmp/.buildx-cache
|
|
||||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-buildx-
|
|
||||||
|
|
||||||
- name: Login to GHCR Docker registry
|
|
||||||
uses: docker/login-action@v1
|
|
||||||
with:
|
|
||||||
registry: ghcr.io
|
|
||||||
username: ${{ github.repository_owner }}
|
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
|
|
||||||
- name: Set outputs
|
|
||||||
run: |
|
|
||||||
echo "RELEASE_TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV \
|
|
||||||
&& echo "RELEASE_SHORT_COMMIT=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Build and push image
|
|
||||||
uses: docker/build-push-action@v2
|
|
||||||
with:
|
|
||||||
context: ./
|
|
||||||
file: ./Dockerfile
|
|
||||||
platforms: linux/amd64
|
|
||||||
push: true
|
|
||||||
build-args: |
|
|
||||||
BUILD=${{ env.RELEASE_SHORT_COMMIT }}
|
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache
|
|
||||||
tags: |
|
|
||||||
ghcr.io/grassrootseconomics/urdt-ussd:latest
|
|
||||||
ghcr.io/grassrootseconomics/urdt-ussd:${{ env.RELEASE_TAG }}
|
|
44
Dockerfile
44
Dockerfile
@ -1,44 +0,0 @@
|
|||||||
FROM golang:1.23.0-bookworm AS build
|
|
||||||
|
|
||||||
ENV CGO_ENABLED=1
|
|
||||||
|
|
||||||
ARG BUILDPLATFORM
|
|
||||||
ARG TARGETPLATFORM
|
|
||||||
ARG BUILD=dev
|
|
||||||
|
|
||||||
WORKDIR /build
|
|
||||||
COPY . .
|
|
||||||
RUN apt update && apt install libgdbm-dev
|
|
||||||
RUN git clone https://git.defalsify.org/vise.git go-vise
|
|
||||||
|
|
||||||
WORKDIR /build/services/registration
|
|
||||||
RUN echo "Compiling go-vise files"
|
|
||||||
RUN make VISE_PATH=/build/go-vise -B
|
|
||||||
|
|
||||||
WORKDIR /build
|
|
||||||
RUN echo "Building on $BUILDPLATFORM, building for $TARGETPLATFORM"
|
|
||||||
RUN go mod download
|
|
||||||
RUN go build -tags logtrace -o ussd-africastalking -ldflags="-X main.build=${BUILD} -s -w" cmd/africastalking/main.go
|
|
||||||
RUN go build -tags logtrace -o ussd-ssh -ldflags="-X main.build=${BUILD} -s -w" cmd/ssh/main.go
|
|
||||||
|
|
||||||
FROM debian:bookworm-slim
|
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
RUN apt update && apt install libgdbm-dev ca-certificates -y
|
|
||||||
RUN apt-get clean && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
WORKDIR /service
|
|
||||||
|
|
||||||
COPY --from=build /build/ussd-africastalking .
|
|
||||||
COPY --from=build /build/ussd-ssh .
|
|
||||||
COPY --from=build /build/LICENSE .
|
|
||||||
COPY --from=build /build/README.md .
|
|
||||||
COPY --from=build /build/services ./services
|
|
||||||
COPY --from=build /build/.env.example .
|
|
||||||
RUN mv .env.example .env
|
|
||||||
|
|
||||||
EXPOSE 7123
|
|
||||||
EXPOSE 7122
|
|
||||||
|
|
||||||
CMD ["./ussd-africastalking"]
|
|
92
README.md
92
README.md
@ -1,92 +0,0 @@
|
|||||||
# URDT USSD service
|
|
||||||
|
|
||||||
This is a USSD service built using the [go-vise](https://github.com/nolash/go-vise) engine.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
### 1. [go-vise](https://github.com/nolash/go-vise)
|
|
||||||
|
|
||||||
Set up `go-vise` by cloning the repository into a separate directory. The main upstream repository is hosted at: `https://git.defalsify.org/vise.git`
|
|
||||||
```
|
|
||||||
git clone https://git.defalsify.org/vise.git
|
|
||||||
```
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
1. Clone the ussd repo in its own directory
|
|
||||||
|
|
||||||
```
|
|
||||||
git clone https://git.grassecon.net/urdt/ussd.git
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Navigate to the project directory.
|
|
||||||
3. Enter the `services/registration` subfolder:
|
|
||||||
```
|
|
||||||
cd services/registration
|
|
||||||
```
|
|
||||||
4. make the .bin files from the .vis files
|
|
||||||
```
|
|
||||||
make VISE_PATH=/var/path/to/your/go-vise -B
|
|
||||||
```
|
|
||||||
5. Return to the project root (`cd ../..`)
|
|
||||||
6. Run the USSD menu
|
|
||||||
```
|
|
||||||
go run cmd/main.go -session-id=0712345678
|
|
||||||
```
|
|
||||||
## Running the different binaries
|
|
||||||
1. ### CLI:
|
|
||||||
```
|
|
||||||
go run cmd/main.go -session-id=0712345678
|
|
||||||
```
|
|
||||||
2. ### Africastalking:
|
|
||||||
```
|
|
||||||
go run cmd/africastalking/main.go
|
|
||||||
```
|
|
||||||
3. ### Async:
|
|
||||||
```
|
|
||||||
go run cmd/async/main.go
|
|
||||||
```
|
|
||||||
4. ### Http:
|
|
||||||
```
|
|
||||||
go run cmd/http/main.go
|
|
||||||
```
|
|
||||||
|
|
||||||
## Flags
|
|
||||||
Below are the supported flags:
|
|
||||||
|
|
||||||
1. `-session-id`:
|
|
||||||
|
|
||||||
Specifies the session ID. (CLI only).
|
|
||||||
|
|
||||||
Default: `075xx2123`.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
go run cmd/main.go -session-id=0712345678
|
|
||||||
```
|
|
||||||
|
|
||||||
2. `-d`:
|
|
||||||
|
|
||||||
Enables engine debug output.
|
|
||||||
|
|
||||||
Default: `false`.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
go run cmd/main.go -session-id=0712345678 -d
|
|
||||||
```
|
|
||||||
|
|
||||||
3. `-db`:
|
|
||||||
|
|
||||||
Specifies the database type.
|
|
||||||
|
|
||||||
Default: `gdbm`.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
go run cmd/main.go -session-id=0712345678 -d -db=postgres
|
|
||||||
```
|
|
||||||
|
|
||||||
>Note: If using `-db=postgres`, ensure PostgreSQL is running with the connection details specified in your `.env` file.
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[AGPL-3.0](LICENSE).
|
|
@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"admins": [
|
|
||||||
{
|
|
||||||
"phonenumber" : "<replace with any admin number to test with >"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/logging"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
logg = logging.NewVanilla().WithDomain("adminstore")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Admin struct {
|
|
||||||
PhoneNumber string `json:"phonenumber"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Admins []Admin `json:"admins"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Seed(ctx context.Context) error {
|
|
||||||
var config Config
|
|
||||||
adminstore, err := utils.NewAdminStore(ctx, "../admin_numbers")
|
|
||||||
store := adminstore.FsStore
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer store.Close()
|
|
||||||
data, err := os.ReadFile("admin_numbers.json")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &config); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, admin := range config.Admins {
|
|
||||||
err := store.Put(ctx, []byte(admin.PhoneNumber), []byte("1"))
|
|
||||||
if err != nil {
|
|
||||||
logg.Printf(logging.LVL_DEBUG, "Failed to insert admin number", admin.PhoneNumber)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/cmd/admin/commands"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
ctx := context.Background()
|
|
||||||
err := commands.Seed(ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to initialize a list of admins with error %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
126
cmd/lang/main.go
126
cmd/lang/main.go
@ -1,126 +0,0 @@
|
|||||||
// create language files from environment
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/logging"
|
|
||||||
"git.defalsify.org/vise.git/lang"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/config"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/initializers"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
|
|
||||||
changeHeadSrc = `LOAD reset_account_authorized 0
|
|
||||||
LOAD reset_incorrect 0
|
|
||||||
CATCH incorrect_pin flag_incorrect_pin 1
|
|
||||||
CATCH pin_entry flag_account_authorized 0
|
|
||||||
`
|
|
||||||
|
|
||||||
selectSrc = `LOAD set_language 6
|
|
||||||
RELOAD set_language
|
|
||||||
CATCH terms flag_account_created 0
|
|
||||||
MOVE language_changed
|
|
||||||
`
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
logg = logging.NewVanilla()
|
|
||||||
mouts string
|
|
||||||
incmps string
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
initializers.LoadEnvVariables()
|
|
||||||
}
|
|
||||||
|
|
||||||
func toLanguageLabel(ln lang.Language) string {
|
|
||||||
s := ln.Name
|
|
||||||
v := strings.Split(s, " (")
|
|
||||||
if len(v) > 1 {
|
|
||||||
s = v[0]
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func toLanguageKey(ln lang.Language) string {
|
|
||||||
s := toLanguageLabel(ln)
|
|
||||||
return strings.ToLower(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var srcDir string
|
|
||||||
|
|
||||||
flag.StringVar(&srcDir, "o", ".", "resource dir write to")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
logg.Infof("start command", "dir", srcDir)
|
|
||||||
|
|
||||||
err := config.LoadConfig()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "config load error: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
logg.Tracef("using languages", "lang", config.Languages)
|
|
||||||
|
|
||||||
for i, v := range(config.Languages) {
|
|
||||||
ln, err := lang.LanguageFromCode(v)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "error parsing language: %s\n", v)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
n := i + 1
|
|
||||||
s := toLanguageKey(ln)
|
|
||||||
mouts += fmt.Sprintf("MOUT %s %v\n", s, n)
|
|
||||||
v = "set_" + ln.Code
|
|
||||||
incmps += fmt.Sprintf("INCMP %s %v\n", v, n)
|
|
||||||
|
|
||||||
p := path.Join(srcDir, v)
|
|
||||||
w, err := os.OpenFile(p, os.O_WRONLY | os.O_CREATE | os.O_EXCL, 0600)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "failed open language set template output: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
s = toLanguageLabel(ln)
|
|
||||||
defer w.Close()
|
|
||||||
_, err = w.Write([]byte(s))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "failed write select language vis output: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
src := mouts + "HALT\n" + incmps
|
|
||||||
src += "INCMP . *\n"
|
|
||||||
|
|
||||||
p := path.Join(srcDir, "select_language.vis")
|
|
||||||
w, err := os.OpenFile(p, os.O_WRONLY | os.O_CREATE | os.O_EXCL, 0600)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "failed open select language vis output: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer w.Close()
|
|
||||||
_, err = w.Write([]byte(src))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "failed write select language vis output: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
src = changeHeadSrc + src
|
|
||||||
p = path.Join(srcDir, "change_language.vis")
|
|
||||||
w, err = os.OpenFile(p, os.O_WRONLY | os.O_CREATE | os.O_EXCL, 0600)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "failed open select language vis output: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
defer w.Close()
|
|
||||||
_, err = w.Write([]byte(src))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "failed write select language vis output: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/config"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/initializers"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/debug"
|
|
||||||
"git.defalsify.org/vise.git/db"
|
|
||||||
"git.defalsify.org/vise.git/logging"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
logg = logging.NewVanilla()
|
|
||||||
scriptDir = path.Join("services", "registration")
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
initializers.LoadEnvVariables()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func formatItem(k []byte, v []byte) (string, error) {
|
|
||||||
o, err := debug.FromKey(k)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
s := fmt.Sprintf("%vValue: %v\n\n", o, string(v))
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
config.LoadConfig()
|
|
||||||
|
|
||||||
var connStr string
|
|
||||||
var sessionId string
|
|
||||||
var database string
|
|
||||||
var engineDebug bool
|
|
||||||
var err error
|
|
||||||
|
|
||||||
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
|
||||||
flag.StringVar(&connStr, "c", ".state", "connection string")
|
|
||||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if connStr != "" {
|
|
||||||
connStr = config.DbConn
|
|
||||||
}
|
|
||||||
connData, err := storage.ToConnData(config.DbConn)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
logg.Infof("start command", "conn", connData)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
|
||||||
ctx = context.WithValue(ctx, "Database", database)
|
|
||||||
|
|
||||||
resourceDir := scriptDir
|
|
||||||
menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
|
|
||||||
|
|
||||||
store, err := menuStorageService.GetUserdataDb(ctx)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "get userdata db: %v\n", err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
store.SetPrefix(db.DATATYPE_USERDATA)
|
|
||||||
|
|
||||||
d, err := store.Dump(ctx, []byte(sessionId))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "store dump fail: %v\n", err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
for true {
|
|
||||||
k, v := d.Next(ctx)
|
|
||||||
if k == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
r, err := formatItem(k, v)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "format db item error: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
fmt.Printf(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = store.Close()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,90 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"crypto/sha1"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
|
|
||||||
testdataloader "github.com/peteole/testdata-loader"
|
|
||||||
"git.defalsify.org/vise.git/logging"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/config"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/initializers"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
logg = logging.NewVanilla()
|
|
||||||
baseDir = testdataloader.GetBasePath()
|
|
||||||
scriptDir = path.Join("services", "registration")
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
initializers.LoadEnvVariables()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
config.LoadConfig()
|
|
||||||
|
|
||||||
var connStr string
|
|
||||||
var sessionId string
|
|
||||||
var database string
|
|
||||||
var engineDebug bool
|
|
||||||
var err error
|
|
||||||
|
|
||||||
flag.StringVar(&sessionId, "session-id", "075xx2123", "session id")
|
|
||||||
flag.StringVar(&connStr, "c", "", "connection string")
|
|
||||||
flag.BoolVar(&engineDebug, "d", false, "use engine debug output")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if connStr != "" {
|
|
||||||
connStr = config.DbConn
|
|
||||||
}
|
|
||||||
connData, err := storage.ToConnData(config.DbConn)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "connstr err: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
logg.Infof("start command", "conn", connData)
|
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx = context.WithValue(ctx, "SessionId", sessionId)
|
|
||||||
ctx = context.WithValue(ctx, "Database", database)
|
|
||||||
|
|
||||||
resourceDir := scriptDir
|
|
||||||
menuStorageService := storage.NewMenuStorageService(connData, resourceDir)
|
|
||||||
|
|
||||||
store, err := menuStorageService.GetUserdataDb(ctx)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
userStore := common.UserDataStore{store}
|
|
||||||
|
|
||||||
h := sha1.New()
|
|
||||||
h.Write([]byte(sessionId))
|
|
||||||
address := h.Sum(nil)
|
|
||||||
addressString := fmt.Sprintf("%x", address)
|
|
||||||
|
|
||||||
err = userStore.WriteEntry(ctx, sessionId, common.DATA_PUBLIC_KEY, []byte(addressString))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = userStore.WriteEntry(ctx, addressString, common.DATA_PUBLIC_KEY_REVERSE, []byte(sessionId))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = store.Close()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, err.Error())
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
134
common/db.go
134
common/db.go
@ -1,134 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/logging"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DataType is a subprefix value used in association with vise/db.DATATYPE_USERDATA.
|
|
||||||
//
|
|
||||||
// All keys are used only within the context of a single account. Unless otherwise specified, the user context is the session id.
|
|
||||||
//
|
|
||||||
// * The first byte is vise/db.DATATYPE_USERDATA
|
|
||||||
// * The last 2 bytes are the DataTyp value, big-endian.
|
|
||||||
// * The intermediate bytes are the id of the user context.
|
|
||||||
//
|
|
||||||
// All values are strings
|
|
||||||
type DataTyp uint16
|
|
||||||
|
|
||||||
const (
|
|
||||||
// API Tracking id to follow status of account creation
|
|
||||||
DATA_TRACKING_ID = iota
|
|
||||||
// EVM address returned from API on account creation
|
|
||||||
DATA_PUBLIC_KEY
|
|
||||||
// Currently active PIN used to authenticate ussd state change requests
|
|
||||||
DATA_ACCOUNT_PIN
|
|
||||||
// The first name of the user
|
|
||||||
DATA_FIRST_NAME
|
|
||||||
// The last name of the user
|
|
||||||
DATA_FAMILY_NAME
|
|
||||||
// The year-of-birth of the user
|
|
||||||
DATA_YOB
|
|
||||||
// The location of the user
|
|
||||||
DATA_LOCATION
|
|
||||||
// The gender of the user
|
|
||||||
DATA_GENDER
|
|
||||||
// The offerings description of the user
|
|
||||||
DATA_OFFERINGS
|
|
||||||
// The ethereum address of the recipient of an ongoing send request
|
|
||||||
DATA_RECIPIENT
|
|
||||||
// The voucher value amount of an ongoing send request
|
|
||||||
DATA_AMOUNT
|
|
||||||
// A general swap field for temporary values
|
|
||||||
DATA_TEMPORARY_VALUE
|
|
||||||
// Currently active voucher symbol of user
|
|
||||||
DATA_ACTIVE_SYM
|
|
||||||
// Voucher balance of user's currently active voucher
|
|
||||||
DATA_ACTIVE_BAL
|
|
||||||
// String boolean indicating whether use of PIN is blocked
|
|
||||||
DATA_BLOCKED_NUMBER
|
|
||||||
// Reverse mapping of a user's evm address to a session id.
|
|
||||||
DATA_PUBLIC_KEY_REVERSE
|
|
||||||
// Decimal count of the currently active voucher
|
|
||||||
DATA_ACTIVE_DECIMAL
|
|
||||||
// EVM address of the currently active voucher
|
|
||||||
DATA_ACTIVE_ADDRESS
|
|
||||||
//Holds count of the number of incorrect PIN attempts
|
|
||||||
DATA_INCORRECT_PIN_ATTEMPTS
|
|
||||||
//ISO 639 code for the selected language.
|
|
||||||
DATA_SELECTED_LANGUAGE_CODE
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// List of valid voucher symbols in the user context.
|
|
||||||
DATA_VOUCHER_SYMBOLS DataTyp = 256 + iota
|
|
||||||
// List of voucher balances for vouchers valid in the user context.
|
|
||||||
DATA_VOUCHER_BALANCES
|
|
||||||
// List of voucher decimal counts for vouchers valid in the user context.
|
|
||||||
DATA_VOUCHER_DECIMALS
|
|
||||||
// List of voucher EVM addresses for vouchers valid in the user context.
|
|
||||||
DATA_VOUCHER_ADDRESSES
|
|
||||||
// List of senders for valid transactions in the user context.
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DATA_TX_SENDERS = 512 + iota
|
|
||||||
// List of recipients for valid transactions in the user context.
|
|
||||||
DATA_TX_RECIPIENTS
|
|
||||||
// List of voucher values for valid transactions in the user context.
|
|
||||||
DATA_TX_VALUES
|
|
||||||
// List of voucher EVM addresses for valid transactions in the user context.
|
|
||||||
DATA_TX_ADDRESSES
|
|
||||||
// List of valid transaction hashes in the user context.
|
|
||||||
DATA_TX_HASHES
|
|
||||||
// List of transaction dates for valid transactions in the user context.
|
|
||||||
DATA_TX_DATES
|
|
||||||
// List of voucher symbols for valid transactions in the user context.
|
|
||||||
DATA_TX_SYMBOLS
|
|
||||||
// List of voucher decimal counts for valid transactions in the user context.
|
|
||||||
DATA_TX_DECIMALS
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
logg = logging.NewVanilla().WithDomain("urdt-common")
|
|
||||||
)
|
|
||||||
|
|
||||||
func typToBytes(typ DataTyp) []byte {
|
|
||||||
var b [2]byte
|
|
||||||
binary.BigEndian.PutUint16(b[:], uint16(typ))
|
|
||||||
return b[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func PackKey(typ DataTyp, data []byte) []byte {
|
|
||||||
v := typToBytes(typ)
|
|
||||||
return append(v, data...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringToDataTyp(str string) (DataTyp, error) {
|
|
||||||
switch str {
|
|
||||||
case "DATA_FIRST_NAME":
|
|
||||||
return DATA_FIRST_NAME, nil
|
|
||||||
case "DATA_FAMILY_NAME":
|
|
||||||
return DATA_FAMILY_NAME, nil
|
|
||||||
case "DATA_YOB":
|
|
||||||
return DATA_YOB, nil
|
|
||||||
case "DATA_LOCATION":
|
|
||||||
return DATA_LOCATION, nil
|
|
||||||
case "DATA_GENDER":
|
|
||||||
return DATA_GENDER, nil
|
|
||||||
case "DATA_OFFERINGS":
|
|
||||||
return DATA_OFFERINGS, nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
return 0, errors.New("invalid DataTyp string")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToBytes converts DataTyp or int to a byte slice
|
|
||||||
func ToBytes[T ~uint16 | int](value T) []byte {
|
|
||||||
bytes := make([]byte, 2)
|
|
||||||
binary.BigEndian.PutUint16(bytes, uint16(value))
|
|
||||||
return bytes
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func NormalizeHex(s string) (string, error) {
|
|
||||||
if len(s) >= 2 {
|
|
||||||
if s[:2] == "0x" {
|
|
||||||
s = s[2:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r, err := hex.DecodeString(s)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return hex.EncodeToString(r), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsSameHex(left string, right string) bool {
|
|
||||||
bl, err := NormalizeHex(left)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
br, err := NormalizeHex(left)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return strings.Compare(bl, br) == 0
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Define the regex pattern as a constant
|
|
||||||
pinPattern = `^\d{4}$`
|
|
||||||
|
|
||||||
//Allowed incorrect PIN attempts
|
|
||||||
AllowedPINAttempts = uint8(3)
|
|
||||||
|
|
||||||
)
|
|
||||||
|
|
||||||
// checks whether the given input is a 4 digit number
|
|
||||||
func IsValidPIN(pin string) bool {
|
|
||||||
match, _ := regexp.MatchString(pinPattern, pin)
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
|
|
||||||
// HashPIN uses bcrypt with 8 salt rounds to hash the PIN
|
|
||||||
func HashPIN(pin string) (string, error) {
|
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(pin), 8)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(hash), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// VerifyPIN compareS the hashed PIN with the plaintext PIN
|
|
||||||
func VerifyPIN(hashedPIN, pin string) bool {
|
|
||||||
err := bcrypt.CompareHashAndPassword([]byte(hashedPIN), []byte(pin))
|
|
||||||
return err == nil
|
|
||||||
}
|
|
@ -1,173 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIsValidPIN(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
pin string
|
|
||||||
expected bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Valid PIN with 4 digits",
|
|
||||||
pin: "1234",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Valid PIN with leading zeros",
|
|
||||||
pin: "0001",
|
|
||||||
expected: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid PIN with less than 4 digits",
|
|
||||||
pin: "123",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid PIN with more than 4 digits",
|
|
||||||
pin: "12345",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid PIN with letters",
|
|
||||||
pin: "abcd",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid PIN with special characters",
|
|
||||||
pin: "12@#",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty PIN",
|
|
||||||
pin: "",
|
|
||||||
expected: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
actual := IsValidPIN(tt.pin)
|
|
||||||
if actual != tt.expected {
|
|
||||||
t.Errorf("IsValidPIN(%q) = %v; expected %v", tt.pin, actual, tt.expected)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHashPIN(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
pin string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Valid PIN with 4 digits",
|
|
||||||
pin: "1234",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Valid PIN with leading zeros",
|
|
||||||
pin: "0001",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Empty PIN",
|
|
||||||
pin: "",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
hashedPIN, err := HashPIN(tt.pin)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("HashPIN(%q) returned an error: %v", tt.pin, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if hashedPIN == "" {
|
|
||||||
t.Errorf("HashPIN(%q) returned an empty hash", tt.pin)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the hash can be verified with bcrypt
|
|
||||||
err = bcrypt.CompareHashAndPassword([]byte(hashedPIN), []byte(tt.pin))
|
|
||||||
if tt.pin != "" && err != nil {
|
|
||||||
t.Errorf("HashPIN(%q) produced a hash that does not match: %v", tt.pin, err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerifyMigratedHashPin(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
pin string
|
|
||||||
hash string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
pin: "1234",
|
|
||||||
hash: "$2b$08$dTvIGxCCysJtdvrSnaLStuylPoOS/ZLYYkxvTeR5QmTFY3TSvPQC6",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.pin, func(t *testing.T) {
|
|
||||||
ok := VerifyPIN(tt.hash, tt.pin)
|
|
||||||
if !ok {
|
|
||||||
t.Errorf("VerifyPIN could not verify migrated PIN: %v", tt.pin)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVerifyPIN(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
pin string
|
|
||||||
hashedPIN string
|
|
||||||
shouldPass bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Valid PIN verification",
|
|
||||||
pin: "1234",
|
|
||||||
hashedPIN: hashPINHelper("1234"),
|
|
||||||
shouldPass: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid PIN verification with incorrect PIN",
|
|
||||||
pin: "5678",
|
|
||||||
hashedPIN: hashPINHelper("1234"),
|
|
||||||
shouldPass: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid PIN verification with empty PIN",
|
|
||||||
pin: "",
|
|
||||||
hashedPIN: hashPINHelper("1234"),
|
|
||||||
shouldPass: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Invalid PIN verification with invalid hash",
|
|
||||||
pin: "1234",
|
|
||||||
hashedPIN: "invalidhash",
|
|
||||||
shouldPass: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
result := VerifyPIN(tt.hashedPIN, tt.pin)
|
|
||||||
if result != tt.shouldPass {
|
|
||||||
t.Errorf("VerifyPIN(%q, %q) = %v; expected %v", tt.hashedPIN, tt.pin, result, tt.shouldPass)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to hash a PIN for testing purposes
|
|
||||||
func hashPINHelper(pin string) string {
|
|
||||||
hashedPIN, err := HashPIN(pin)
|
|
||||||
if err != nil {
|
|
||||||
panic("Failed to hash PIN for test setup: " + err.Error())
|
|
||||||
}
|
|
||||||
return hashedPIN
|
|
||||||
}
|
|
@ -1,73 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Define the regex patterns as constants
|
|
||||||
const (
|
|
||||||
phoneRegex = `^(?:\+254|254|0)?((?:7[0-9]{8})|(?:1[01][0-9]{7}))$`
|
|
||||||
addressRegex = `^0x[a-fA-F0-9]{40}$`
|
|
||||||
aliasRegex = `^[a-zA-Z0-9]+$`
|
|
||||||
)
|
|
||||||
|
|
||||||
// IsValidPhoneNumber checks if the given number is a valid phone number
|
|
||||||
func IsValidPhoneNumber(phonenumber string) bool {
|
|
||||||
match, _ := regexp.MatchString(phoneRegex, phonenumber)
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValidAddress checks if the given address is a valid Ethereum address
|
|
||||||
func IsValidAddress(address string) bool {
|
|
||||||
match, _ := regexp.MatchString(addressRegex, address)
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsValidAlias checks if the alias is a valid alias format
|
|
||||||
func IsValidAlias(alias string) bool {
|
|
||||||
match, _ := regexp.MatchString(aliasRegex, alias)
|
|
||||||
return match
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckRecipient validates the recipient format based on the criteria
|
|
||||||
func CheckRecipient(recipient string) (string, error) {
|
|
||||||
if IsValidPhoneNumber(recipient) {
|
|
||||||
return "phone number", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if IsValidAddress(recipient) {
|
|
||||||
return "address", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if IsValidAlias(recipient) {
|
|
||||||
return "alias", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", fmt.Errorf("invalid recipient: must be a phone number, address or alias")
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatPhoneNumber formats a Kenyan phone number to "+254xxxxxxxx".
|
|
||||||
func FormatPhoneNumber(phone string) (string, error) {
|
|
||||||
if !IsValidPhoneNumber(phone) {
|
|
||||||
return "", errors.New("invalid phone number")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove any leading "+" and spaces
|
|
||||||
phone = strings.TrimPrefix(phone, "+")
|
|
||||||
phone = strings.ReplaceAll(phone, " ", "")
|
|
||||||
|
|
||||||
// Replace leading "0" with "254" if present
|
|
||||||
if strings.HasPrefix(phone, "0") {
|
|
||||||
phone = "254" + phone[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add "+" if not already present
|
|
||||||
if !strings.HasPrefix(phone, "254") {
|
|
||||||
return "", errors.New("unexpected format")
|
|
||||||
}
|
|
||||||
|
|
||||||
return "+" + phone, nil
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/db"
|
|
||||||
"git.defalsify.org/vise.git/resource"
|
|
||||||
"git.defalsify.org/vise.git/persist"
|
|
||||||
"git.defalsify.org/vise.git/lang"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
|
||||||
dbstorage "git.grassecon.net/grassrootseconomics/visedriver/storage/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ToConnData = storage.ToConnData
|
|
||||||
)
|
|
||||||
|
|
||||||
func StoreToDb(store *UserDataStore) db.Db {
|
|
||||||
return store.Db
|
|
||||||
}
|
|
||||||
|
|
||||||
func StoreToPrefixDb(store *UserDataStore, pfx []byte) dbstorage.PrefixDb {
|
|
||||||
return dbstorage.NewSubPrefixDb(store.Db, pfx)
|
|
||||||
}
|
|
||||||
|
|
||||||
type StorageServices interface {
|
|
||||||
GetPersister(ctx context.Context) (*persist.Persister, error)
|
|
||||||
GetUserdataDb(ctx context.Context) (db.Db, error)
|
|
||||||
GetResource(ctx context.Context) (resource.Resource, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type StorageService struct {
|
|
||||||
svc *storage.MenuStorageService
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewStorageService(conn storage.ConnData) (*StorageService, error) {
|
|
||||||
svc := &StorageService{
|
|
||||||
svc: storage.NewMenuStorageService(conn, ""),
|
|
||||||
}
|
|
||||||
return svc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ss *StorageService) WithGettext(path string, lns []lang.Language) *StorageService {
|
|
||||||
ss.svc = ss.svc.WithGettext(path, lns)
|
|
||||||
return ss
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: simplify enable poresource, conndata instead
|
|
||||||
func(ss *StorageService) SetResourceDir(resourceDir string) error {
|
|
||||||
ss.svc = ss.svc.WithResourceDir(resourceDir)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func(ss *StorageService) GetPersister(ctx context.Context) (*persist.Persister, error) {
|
|
||||||
return ss.svc.GetPersister(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func(ss *StorageService) GetUserdataDb(ctx context.Context) (db.Db, error) {
|
|
||||||
return ss.svc.GetUserdataDb(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func(ss *StorageService) GetResource(ctx context.Context) (resource.Resource, error) {
|
|
||||||
return nil, errors.New("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func(ss *StorageService) GetStateStore(ctx context.Context) (db.Db, error) {
|
|
||||||
return ss.svc.GetStateStore(ctx)
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"math/big"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TransactionData struct {
|
|
||||||
TemporaryValue string
|
|
||||||
ActiveSym string
|
|
||||||
Amount string
|
|
||||||
PublicKey string
|
|
||||||
Recipient string
|
|
||||||
ActiveDecimal string
|
|
||||||
ActiveAddress string
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseAndScaleAmount(storedAmount, activeDecimal string) (string, error) {
|
|
||||||
// Parse token decimal
|
|
||||||
tokenDecimal, err := strconv.Atoi(activeDecimal)
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse amount
|
|
||||||
amount, _, err := big.ParseFloat(storedAmount, 10, 0, big.ToZero)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scale the amount
|
|
||||||
multiplier := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(tokenDecimal)), nil))
|
|
||||||
finalAmount := new(big.Float).Mul(amount, multiplier)
|
|
||||||
|
|
||||||
// Convert finalAmount to a string
|
|
||||||
finalAmountStr := new(big.Int)
|
|
||||||
finalAmount.Int(finalAmountStr)
|
|
||||||
|
|
||||||
return finalAmountStr.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadTransactionData(ctx context.Context, store DataStore, sessionId string) (TransactionData, error) {
|
|
||||||
data := TransactionData{}
|
|
||||||
fieldToKey := map[string]DataTyp{
|
|
||||||
"TemporaryValue": DATA_TEMPORARY_VALUE,
|
|
||||||
"ActiveSym": DATA_ACTIVE_SYM,
|
|
||||||
"Amount": DATA_AMOUNT,
|
|
||||||
"PublicKey": DATA_PUBLIC_KEY,
|
|
||||||
"Recipient": DATA_RECIPIENT,
|
|
||||||
"ActiveDecimal": DATA_ACTIVE_DECIMAL,
|
|
||||||
"ActiveAddress": DATA_ACTIVE_ADDRESS,
|
|
||||||
}
|
|
||||||
|
|
||||||
v := reflect.ValueOf(&data).Elem()
|
|
||||||
for fieldName, key := range fieldToKey {
|
|
||||||
field := v.FieldByName(fieldName)
|
|
||||||
if !field.IsValid() || !field.CanSet() {
|
|
||||||
return data, errors.New("invalid struct field: " + fieldName)
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := readStringEntry(ctx, store, sessionId, key)
|
|
||||||
if err != nil {
|
|
||||||
return data, err
|
|
||||||
}
|
|
||||||
field.SetString(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readStringEntry(ctx context.Context, store DataStore, sessionId string, key DataTyp) (string, error) {
|
|
||||||
entry, err := store.ReadEntry(ctx, sessionId, key)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(entry), nil
|
|
||||||
}
|
|
@ -1,129 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/alecthomas/assert/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestParseAndScaleAmount(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
amount string
|
|
||||||
decimals string
|
|
||||||
want string
|
|
||||||
expectError bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "whole number",
|
|
||||||
amount: "123",
|
|
||||||
decimals: "2",
|
|
||||||
want: "12300",
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "decimal number",
|
|
||||||
amount: "123.45",
|
|
||||||
decimals: "2",
|
|
||||||
want: "12345",
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "zero decimals",
|
|
||||||
amount: "123.45",
|
|
||||||
decimals: "0",
|
|
||||||
want: "123",
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "large number",
|
|
||||||
amount: "1000000.01",
|
|
||||||
decimals: "6",
|
|
||||||
want: "1000000010000",
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid amount",
|
|
||||||
amount: "abc",
|
|
||||||
decimals: "2",
|
|
||||||
want: "",
|
|
||||||
expectError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid decimals",
|
|
||||||
amount: "123.45",
|
|
||||||
decimals: "abc",
|
|
||||||
want: "",
|
|
||||||
expectError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "zero amount",
|
|
||||||
amount: "0",
|
|
||||||
decimals: "2",
|
|
||||||
want: "0",
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := ParseAndScaleAmount(tt.amount, tt.decimals)
|
|
||||||
|
|
||||||
// Check error cases
|
|
||||||
if tt.expectError {
|
|
||||||
if err == nil {
|
|
||||||
t.Errorf("ParseAndScaleAmount(%q, %q) expected error, got nil", tt.amount, tt.decimals)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("ParseAndScaleAmount(%q, %q) unexpected error: %v", tt.amount, tt.decimals, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("ParseAndScaleAmount(%q, %q) = %v, want %v", tt.amount, tt.decimals, got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReadTransactionData(t *testing.T) {
|
|
||||||
sessionId := "session123"
|
|
||||||
publicKey := "0X13242618721"
|
|
||||||
ctx, store := InitializeTestDb(t)
|
|
||||||
|
|
||||||
// Test transaction data
|
|
||||||
transactionData := map[DataTyp]string{
|
|
||||||
DATA_TEMPORARY_VALUE: "0712345678",
|
|
||||||
DATA_ACTIVE_SYM: "SRF",
|
|
||||||
DATA_AMOUNT: "1000000",
|
|
||||||
DATA_PUBLIC_KEY: publicKey,
|
|
||||||
DATA_RECIPIENT: "0x41c188d63Qa",
|
|
||||||
DATA_ACTIVE_DECIMAL: "6",
|
|
||||||
DATA_ACTIVE_ADDRESS: "0xd4c288865Ce",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the data
|
|
||||||
for key, value := range transactionData {
|
|
||||||
if err := store.WriteEntry(ctx, sessionId, key, []byte(value)); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedResult := TransactionData{
|
|
||||||
TemporaryValue: "0712345678",
|
|
||||||
ActiveSym: "SRF",
|
|
||||||
Amount: "1000000",
|
|
||||||
PublicKey: publicKey,
|
|
||||||
Recipient: "0x41c188d63Qa",
|
|
||||||
ActiveDecimal: "6",
|
|
||||||
ActiveAddress: "0xd4c288865Ce",
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := ReadTransactionData(ctx, store, sessionId)
|
|
||||||
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, expectedResult, data)
|
|
||||||
}
|
|
@ -1,119 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
dbstorage "git.grassecon.net/grassrootseconomics/visedriver/storage/db"
|
|
||||||
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TransferMetadata helps organize data fields
|
|
||||||
type TransferMetadata struct {
|
|
||||||
Senders string
|
|
||||||
Recipients string
|
|
||||||
TransferValues string
|
|
||||||
Addresses string
|
|
||||||
TxHashes string
|
|
||||||
Dates string
|
|
||||||
Symbols string
|
|
||||||
Decimals string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessTransfers converts transfers into formatted strings
|
|
||||||
func ProcessTransfers(transfers []dataserviceapi.Last10TxResponse) TransferMetadata {
|
|
||||||
var data TransferMetadata
|
|
||||||
var senders, recipients, transferValues, addresses, txHashes, dates, symbols, decimals []string
|
|
||||||
|
|
||||||
for _, t := range transfers {
|
|
||||||
senders = append(senders, t.Sender)
|
|
||||||
recipients = append(recipients, t.Recipient)
|
|
||||||
|
|
||||||
// Scale down the amount
|
|
||||||
scaledBalance := ScaleDownBalance(t.TransferValue, t.TokenDecimals)
|
|
||||||
transferValues = append(transferValues, scaledBalance)
|
|
||||||
|
|
||||||
addresses = append(addresses, t.ContractAddress)
|
|
||||||
txHashes = append(txHashes, t.TxHash)
|
|
||||||
dates = append(dates, fmt.Sprintf("%s", t.DateBlock))
|
|
||||||
symbols = append(symbols, t.TokenSymbol)
|
|
||||||
decimals = append(decimals, t.TokenDecimals)
|
|
||||||
}
|
|
||||||
|
|
||||||
data.Senders = strings.Join(senders, "\n")
|
|
||||||
data.Recipients = strings.Join(recipients, "\n")
|
|
||||||
data.TransferValues = strings.Join(transferValues, "\n")
|
|
||||||
data.Addresses = strings.Join(addresses, "\n")
|
|
||||||
data.TxHashes = strings.Join(txHashes, "\n")
|
|
||||||
data.Dates = strings.Join(dates, "\n")
|
|
||||||
data.Symbols = strings.Join(symbols, "\n")
|
|
||||||
data.Decimals = strings.Join(decimals, "\n")
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTransferData retrieves and matches transfer data
|
|
||||||
// returns a formatted string of the full transaction/statement
|
|
||||||
func GetTransferData(ctx context.Context, db dbstorage.PrefixDb, publicKey string, index int) (string, error) {
|
|
||||||
keys := []DataTyp{DATA_TX_SENDERS, DATA_TX_RECIPIENTS, DATA_TX_VALUES, DATA_TX_ADDRESSES, DATA_TX_HASHES, DATA_TX_DATES, DATA_TX_SYMBOLS}
|
|
||||||
data := make(map[DataTyp]string)
|
|
||||||
|
|
||||||
for _, key := range keys {
|
|
||||||
value, err := db.Get(ctx, ToBytes(key))
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("failed to get %s: %v", ToBytes(key), err)
|
|
||||||
}
|
|
||||||
data[key] = string(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split the data
|
|
||||||
senders := strings.Split(string(data[DATA_TX_SENDERS]), "\n")
|
|
||||||
recipients := strings.Split(string(data[DATA_TX_RECIPIENTS]), "\n")
|
|
||||||
values := strings.Split(string(data[DATA_TX_VALUES]), "\n")
|
|
||||||
addresses := strings.Split(string(data[DATA_TX_ADDRESSES]), "\n")
|
|
||||||
hashes := strings.Split(string(data[DATA_TX_HASHES]), "\n")
|
|
||||||
dates := strings.Split(string(data[DATA_TX_DATES]), "\n")
|
|
||||||
syms := strings.Split(string(data[DATA_TX_SYMBOLS]), "\n")
|
|
||||||
|
|
||||||
// Check if index is within range
|
|
||||||
if index < 1 || index > len(senders) {
|
|
||||||
return "", fmt.Errorf("transaction not found: index %d out of range", index)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adjust for 0-based indexing
|
|
||||||
i := index - 1
|
|
||||||
transactionType := "Received"
|
|
||||||
party := fmt.Sprintf("From: %s", strings.TrimSpace(senders[i]))
|
|
||||||
if strings.TrimSpace(senders[i]) == publicKey {
|
|
||||||
transactionType = "Sent"
|
|
||||||
party = fmt.Sprintf("To: %s", strings.TrimSpace(recipients[i]))
|
|
||||||
}
|
|
||||||
|
|
||||||
formattedDate := formatDate(strings.TrimSpace(dates[i]))
|
|
||||||
|
|
||||||
// Build the full transaction detail
|
|
||||||
detail := fmt.Sprintf(
|
|
||||||
"%s %s %s\n%s\nContract address: %s\nTxhash: %s\nDate: %s",
|
|
||||||
transactionType,
|
|
||||||
strings.TrimSpace(values[i]),
|
|
||||||
strings.TrimSpace(syms[i]),
|
|
||||||
party,
|
|
||||||
strings.TrimSpace(addresses[i]),
|
|
||||||
strings.TrimSpace(hashes[i]),
|
|
||||||
formattedDate,
|
|
||||||
)
|
|
||||||
|
|
||||||
return detail, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to format date in desired output
|
|
||||||
func formatDate(dateStr string) string {
|
|
||||||
parsedDate, err := time.Parse("2006-01-02 15:04:05 -0700 MST", dateStr)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Error parsing date:", err)
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return parsedDate.Format("2006-01-02 03:04:05 PM")
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DataStore interface {
|
|
||||||
db.Db
|
|
||||||
ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error)
|
|
||||||
WriteEntry(ctx context.Context, sessionId string, typ DataTyp, value []byte) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserDataStore struct {
|
|
||||||
db.Db
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadEntry retrieves an entry to the userdata store.
|
|
||||||
func (store *UserDataStore) ReadEntry(ctx context.Context, sessionId string, typ DataTyp) ([]byte, error) {
|
|
||||||
store.SetPrefix(db.DATATYPE_USERDATA)
|
|
||||||
store.SetSession(sessionId)
|
|
||||||
k := ToBytes(typ)
|
|
||||||
return store.Get(ctx, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteEntry adds an entry to the userdata store.
|
|
||||||
// BUG: this uses sessionId twice
|
|
||||||
func (store *UserDataStore) WriteEntry(ctx context.Context, sessionId string, typ DataTyp, value []byte) error {
|
|
||||||
store.SetPrefix(db.DATATYPE_USERDATA)
|
|
||||||
store.SetSession(sessionId)
|
|
||||||
k := ToBytes(typ)
|
|
||||||
return store.Put(ctx, k, value)
|
|
||||||
}
|
|
@ -1,174 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
dbstorage "git.grassecon.net/grassrootseconomics/visedriver/storage/db"
|
|
||||||
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// VoucherMetadata helps organize data fields
|
|
||||||
type VoucherMetadata struct {
|
|
||||||
Symbols string
|
|
||||||
Balances string
|
|
||||||
Decimals string
|
|
||||||
Addresses string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessVouchers converts holdings into formatted strings
|
|
||||||
func ProcessVouchers(holdings []dataserviceapi.TokenHoldings) VoucherMetadata {
|
|
||||||
var data VoucherMetadata
|
|
||||||
var symbols, balances, decimals, addresses []string
|
|
||||||
|
|
||||||
for i, h := range holdings {
|
|
||||||
symbols = append(symbols, fmt.Sprintf("%d:%s", i+1, h.TokenSymbol))
|
|
||||||
|
|
||||||
// Scale down the balance
|
|
||||||
scaledBalance := ScaleDownBalance(h.Balance, h.TokenDecimals)
|
|
||||||
|
|
||||||
balances = append(balances, fmt.Sprintf("%d:%s", i+1, scaledBalance))
|
|
||||||
decimals = append(decimals, fmt.Sprintf("%d:%s", i+1, h.TokenDecimals))
|
|
||||||
addresses = append(addresses, fmt.Sprintf("%d:%s", i+1, h.ContractAddress))
|
|
||||||
}
|
|
||||||
|
|
||||||
data.Symbols = strings.Join(symbols, "\n")
|
|
||||||
data.Balances = strings.Join(balances, "\n")
|
|
||||||
data.Decimals = strings.Join(decimals, "\n")
|
|
||||||
data.Addresses = strings.Join(addresses, "\n")
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
func ScaleDownBalance(balance, decimals string) string {
|
|
||||||
// Convert balance and decimals to big.Float
|
|
||||||
bal := new(big.Float)
|
|
||||||
bal.SetString(balance)
|
|
||||||
|
|
||||||
dec, ok := new(big.Int).SetString(decimals, 10)
|
|
||||||
if !ok {
|
|
||||||
dec = big.NewInt(0) // Default to 0 decimals in case of conversion failure
|
|
||||||
}
|
|
||||||
|
|
||||||
divisor := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), dec, nil))
|
|
||||||
scaledBalance := new(big.Float).Quo(bal, divisor)
|
|
||||||
|
|
||||||
// Return the scaled balance without trailing decimals if it's an integer
|
|
||||||
if scaledBalance.IsInt() {
|
|
||||||
return scaledBalance.Text('f', 0)
|
|
||||||
}
|
|
||||||
return scaledBalance.Text('f', -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetVoucherData retrieves and matches voucher data
|
|
||||||
func GetVoucherData(ctx context.Context, db dbstorage.PrefixDb, input string) (*dataserviceapi.TokenHoldings, error) {
|
|
||||||
keys := []DataTyp{DATA_VOUCHER_SYMBOLS, DATA_VOUCHER_BALANCES, DATA_VOUCHER_DECIMALS, DATA_VOUCHER_ADDRESSES}
|
|
||||||
data := make(map[DataTyp]string)
|
|
||||||
|
|
||||||
for _, key := range keys {
|
|
||||||
value, err := db.Get(ctx, ToBytes(key))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to get %s: %v", ToBytes(key), err)
|
|
||||||
}
|
|
||||||
data[key] = string(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
symbol, balance, decimal, address := MatchVoucher(input,
|
|
||||||
data[DATA_VOUCHER_SYMBOLS],
|
|
||||||
data[DATA_VOUCHER_BALANCES],
|
|
||||||
data[DATA_VOUCHER_DECIMALS],
|
|
||||||
data[DATA_VOUCHER_ADDRESSES],
|
|
||||||
)
|
|
||||||
|
|
||||||
if symbol == "" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return &dataserviceapi.TokenHoldings{
|
|
||||||
TokenSymbol: string(symbol),
|
|
||||||
Balance: string(balance),
|
|
||||||
TokenDecimals: string(decimal),
|
|
||||||
ContractAddress: string(address),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchVoucher finds the matching voucher symbol, balance, decimals and contract address based on the input.
|
|
||||||
func MatchVoucher(input, symbols, balances, decimals, addresses string) (symbol, balance, decimal, address string) {
|
|
||||||
symList := strings.Split(symbols, "\n")
|
|
||||||
balList := strings.Split(balances, "\n")
|
|
||||||
decList := strings.Split(decimals, "\n")
|
|
||||||
addrList := strings.Split(addresses, "\n")
|
|
||||||
|
|
||||||
logg.Tracef("found", "symlist", symList, "syms", symbols, "input", input)
|
|
||||||
for i, sym := range symList {
|
|
||||||
parts := strings.SplitN(sym, ":", 2)
|
|
||||||
|
|
||||||
if input == parts[0] || strings.EqualFold(input, parts[1]) {
|
|
||||||
symbol = parts[1]
|
|
||||||
if i < len(balList) {
|
|
||||||
balance = strings.SplitN(balList[i], ":", 2)[1]
|
|
||||||
}
|
|
||||||
if i < len(decList) {
|
|
||||||
decimal = strings.SplitN(decList[i], ":", 2)[1]
|
|
||||||
}
|
|
||||||
if i < len(addrList) {
|
|
||||||
address = strings.SplitN(addrList[i], ":", 2)[1]
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// StoreTemporaryVoucher saves voucher metadata as temporary entries in the DataStore.
|
|
||||||
func StoreTemporaryVoucher(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error {
|
|
||||||
tempData := fmt.Sprintf("%s,%s,%s,%s", data.TokenSymbol, data.Balance, data.TokenDecimals, data.ContractAddress)
|
|
||||||
|
|
||||||
if err := store.WriteEntry(ctx, sessionId, DATA_TEMPORARY_VALUE, []byte(tempData)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetTemporaryVoucherData retrieves temporary voucher metadata from the DataStore.
|
|
||||||
func GetTemporaryVoucherData(ctx context.Context, store DataStore, sessionId string) (*dataserviceapi.TokenHoldings, error) {
|
|
||||||
temp_data, err := store.ReadEntry(ctx, sessionId, DATA_TEMPORARY_VALUE)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
values := strings.SplitN(string(temp_data), ",", 4)
|
|
||||||
|
|
||||||
data := &dataserviceapi.TokenHoldings{}
|
|
||||||
|
|
||||||
data.TokenSymbol = values[0]
|
|
||||||
data.Balance = values[1]
|
|
||||||
data.TokenDecimals = values[2]
|
|
||||||
data.ContractAddress = values[3]
|
|
||||||
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateVoucherData updates the active voucher data in the DataStore.
|
|
||||||
func UpdateVoucherData(ctx context.Context, store DataStore, sessionId string, data *dataserviceapi.TokenHoldings) error {
|
|
||||||
logg.TraceCtxf(ctx, "dtal", "data", data)
|
|
||||||
// Active voucher data entries
|
|
||||||
activeEntries := map[DataTyp][]byte{
|
|
||||||
DATA_ACTIVE_SYM: []byte(data.TokenSymbol),
|
|
||||||
DATA_ACTIVE_BAL: []byte(data.Balance),
|
|
||||||
DATA_ACTIVE_DECIMAL: []byte(data.TokenDecimals),
|
|
||||||
DATA_ACTIVE_ADDRESS: []byte(data.ContractAddress),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write active data
|
|
||||||
for key, value := range activeEntries {
|
|
||||||
if err := store.WriteEntry(ctx, sessionId, key, value); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,200 +0,0 @@
|
|||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/alecthomas/assert/v2"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
visedb "git.defalsify.org/vise.git/db"
|
|
||||||
memdb "git.defalsify.org/vise.git/db/mem"
|
|
||||||
dbstorage "git.grassecon.net/grassrootseconomics/visedriver/storage/db"
|
|
||||||
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
|
|
||||||
)
|
|
||||||
|
|
||||||
// InitializeTestDb sets up and returns an in-memory database and store.
|
|
||||||
func InitializeTestDb(t *testing.T) (context.Context, *UserDataStore) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// Initialize memDb
|
|
||||||
db := memdb.NewMemDb()
|
|
||||||
err := db.Connect(ctx, "")
|
|
||||||
require.NoError(t, err, "Failed to connect to memDb")
|
|
||||||
|
|
||||||
// Create UserDataStore with memDb
|
|
||||||
store := &UserDataStore{Db: db}
|
|
||||||
|
|
||||||
t.Cleanup(func() {
|
|
||||||
db.Close() // Ensure the DB is closed after each test
|
|
||||||
})
|
|
||||||
|
|
||||||
return ctx, store
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMatchVoucher(t *testing.T) {
|
|
||||||
symbols := "1:SRF\n2:MILO"
|
|
||||||
balances := "1:100\n2:200"
|
|
||||||
decimals := "1:6\n2:4"
|
|
||||||
addresses := "1:0xd4c288865Ce\n2:0x41c188d63Qa"
|
|
||||||
|
|
||||||
// Test for valid voucher
|
|
||||||
symbol, balance, decimal, address := MatchVoucher("2", symbols, balances, decimals, addresses)
|
|
||||||
|
|
||||||
// Assertions for valid voucher
|
|
||||||
assert.Equal(t, "MILO", symbol)
|
|
||||||
assert.Equal(t, "200", balance)
|
|
||||||
assert.Equal(t, "4", decimal)
|
|
||||||
assert.Equal(t, "0x41c188d63Qa", address)
|
|
||||||
|
|
||||||
// Test for non-existent voucher
|
|
||||||
symbol, balance, decimal, address = MatchVoucher("3", symbols, balances, decimals, addresses)
|
|
||||||
|
|
||||||
// Assertions for non-match
|
|
||||||
assert.Equal(t, "", symbol)
|
|
||||||
assert.Equal(t, "", balance)
|
|
||||||
assert.Equal(t, "", decimal)
|
|
||||||
assert.Equal(t, "", address)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestProcessVouchers(t *testing.T) {
|
|
||||||
holdings := []dataserviceapi.TokenHoldings{
|
|
||||||
{ContractAddress: "0xd4c288865Ce", TokenSymbol: "SRF", TokenDecimals: "6", Balance: "100000000"},
|
|
||||||
{ContractAddress: "0x41c188d63Qa", TokenSymbol: "MILO", TokenDecimals: "4", Balance: "200000000"},
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedResult := VoucherMetadata{
|
|
||||||
Symbols: "1:SRF\n2:MILO",
|
|
||||||
Balances: "1:100\n2:20000",
|
|
||||||
Decimals: "1:6\n2:4",
|
|
||||||
Addresses: "1:0xd4c288865Ce\n2:0x41c188d63Qa",
|
|
||||||
}
|
|
||||||
|
|
||||||
result := ProcessVouchers(holdings)
|
|
||||||
|
|
||||||
assert.Equal(t, expectedResult, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetVoucherData(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
db := memdb.NewMemDb()
|
|
||||||
err := db.Connect(ctx, "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
prefix := ToBytes(visedb.DATATYPE_USERDATA)
|
|
||||||
spdb := dbstorage.NewSubPrefixDb(db, prefix)
|
|
||||||
|
|
||||||
// Test voucher data
|
|
||||||
mockData := map[DataTyp][]byte{
|
|
||||||
DATA_VOUCHER_SYMBOLS: []byte("1:SRF\n2:MILO"),
|
|
||||||
DATA_VOUCHER_BALANCES: []byte("1:100\n2:200"),
|
|
||||||
DATA_VOUCHER_DECIMALS: []byte("1:6\n2:4"),
|
|
||||||
DATA_VOUCHER_ADDRESSES: []byte("1:0xd4c288865Ce\n2:0x41c188d63Qa"),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Put the data
|
|
||||||
for key, value := range mockData {
|
|
||||||
err = spdb.Put(ctx, []byte(ToBytes(key)), []byte(value))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := GetVoucherData(ctx, spdb, "1")
|
|
||||||
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, "SRF", result.TokenSymbol)
|
|
||||||
assert.Equal(t, "100", result.Balance)
|
|
||||||
assert.Equal(t, "6", result.TokenDecimals)
|
|
||||||
assert.Equal(t, "0xd4c288865Ce", result.ContractAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStoreTemporaryVoucher(t *testing.T) {
|
|
||||||
ctx, store := InitializeTestDb(t)
|
|
||||||
sessionId := "session123"
|
|
||||||
|
|
||||||
// Test data
|
|
||||||
voucherData := &dataserviceapi.TokenHoldings{
|
|
||||||
TokenSymbol: "SRF",
|
|
||||||
Balance: "200",
|
|
||||||
TokenDecimals: "6",
|
|
||||||
ContractAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the function being tested
|
|
||||||
err := StoreTemporaryVoucher(ctx, store, sessionId, voucherData)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Verify stored data
|
|
||||||
expectedData := fmt.Sprintf("%s,%s,%s,%s", "SRF", "200", "6", "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9")
|
|
||||||
|
|
||||||
storedValue, err := store.ReadEntry(ctx, sessionId, DATA_TEMPORARY_VALUE)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expectedData, string(storedValue), "Mismatch for key %v", DATA_TEMPORARY_VALUE)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetTemporaryVoucherData(t *testing.T) {
|
|
||||||
ctx, store := InitializeTestDb(t)
|
|
||||||
sessionId := "session123"
|
|
||||||
|
|
||||||
// Test voucher data
|
|
||||||
tempData := &dataserviceapi.TokenHoldings{
|
|
||||||
TokenSymbol: "SRF",
|
|
||||||
Balance: "200",
|
|
||||||
TokenDecimals: "6",
|
|
||||||
ContractAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the data
|
|
||||||
err := StoreTemporaryVoucher(ctx, store, sessionId, tempData)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Execute the function being tested
|
|
||||||
data, err := GetTemporaryVoucherData(ctx, store, sessionId)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, tempData, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateVoucherData(t *testing.T) {
|
|
||||||
ctx, store := InitializeTestDb(t)
|
|
||||||
sessionId := "session123"
|
|
||||||
|
|
||||||
// New voucher data
|
|
||||||
newData := &dataserviceapi.TokenHoldings{
|
|
||||||
TokenSymbol: "SRF",
|
|
||||||
Balance: "200",
|
|
||||||
TokenDecimals: "6",
|
|
||||||
ContractAddress: "0xd4c288865Ce0985a481Eef3be02443dF5E2e4Ea9",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Old temporary data
|
|
||||||
tempData := &dataserviceapi.TokenHoldings{
|
|
||||||
TokenSymbol: "OLD",
|
|
||||||
Balance: "100",
|
|
||||||
TokenDecimals: "8",
|
|
||||||
ContractAddress: "0xold",
|
|
||||||
}
|
|
||||||
require.NoError(t, StoreTemporaryVoucher(ctx, store, sessionId, tempData))
|
|
||||||
|
|
||||||
// Execute update
|
|
||||||
err := UpdateVoucherData(ctx, store, sessionId, newData)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Verify active data was stored correctly
|
|
||||||
activeEntries := map[DataTyp][]byte{
|
|
||||||
DATA_ACTIVE_SYM: []byte(newData.TokenSymbol),
|
|
||||||
DATA_ACTIVE_BAL: []byte(newData.Balance),
|
|
||||||
DATA_ACTIVE_DECIMAL: []byte(newData.TokenDecimals),
|
|
||||||
DATA_ACTIVE_ADDRESS: []byte(newData.ContractAddress),
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, expectedValue := range activeEntries {
|
|
||||||
storedValue, err := store.ReadEntry(ctx, sessionId, key)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, expectedValue, storedValue, "Active data mismatch for key %v", key)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +1,9 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/initializers"
|
"git.grassecon.net/grassrootseconomics/visedriver/env"
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
createAccountPath = "/api/v2/account/create"
|
|
||||||
trackStatusPath = "/api/track"
|
|
||||||
balancePathPrefix = "/api/account"
|
|
||||||
trackPath = "/api/v2/account/status"
|
|
||||||
tokenTransferPrefix = "/api/v2/token/transfer"
|
|
||||||
voucherHoldingsPathPrefix = "/api/v1/holdings"
|
|
||||||
voucherTransfersPathPrefix = "/api/v1/transfers/last10"
|
|
||||||
voucherDataPathPrefix = "/api/v1/token"
|
|
||||||
AliasPrefix = "api/v1/alias"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -25,29 +12,14 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
custodialURLBase string
|
|
||||||
dataURLBase string
|
|
||||||
BearerToken string
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
CreateAccountURL string
|
|
||||||
TrackStatusURL string
|
|
||||||
BalanceURL string
|
|
||||||
TrackURL string
|
|
||||||
TokenTransferURL string
|
|
||||||
VoucherHoldingsURL string
|
|
||||||
VoucherTransfersURL string
|
|
||||||
VoucherDataURL string
|
|
||||||
CheckAliasURL string
|
|
||||||
DbConn string
|
DbConn string
|
||||||
DefaultLanguage string
|
DefaultLanguage string
|
||||||
Languages []string
|
Languages []string
|
||||||
)
|
)
|
||||||
|
|
||||||
func setLanguage() error {
|
func setLanguage() error {
|
||||||
defaultLanguage = initializers.GetEnv("DEFAULT_LANGUAGE", defaultLanguage)
|
defaultLanguage = env.GetEnv("DEFAULT_LANGUAGE", defaultLanguage)
|
||||||
languages = strings.Split(initializers.GetEnv("LANGUAGES", defaultLanguage), ",")
|
languages = strings.Split(env.GetEnv("LANGUAGES", defaultLanguage), ",")
|
||||||
haveDefaultLanguage := false
|
haveDefaultLanguage := false
|
||||||
for i, v := range(languages) {
|
for i, v := range(languages) {
|
||||||
languages[i] = strings.ReplaceAll(v, " ", "")
|
languages[i] = strings.ReplaceAll(v, " ", "")
|
||||||
@ -63,37 +35,16 @@ func setLanguage() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setBase() error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
custodialURLBase = initializers.GetEnv("CUSTODIAL_URL_BASE", "http://localhost:5003")
|
|
||||||
dataURLBase = initializers.GetEnv("DATA_URL_BASE", "http://localhost:5006")
|
|
||||||
BearerToken = initializers.GetEnv("BEARER_TOKEN", "")
|
|
||||||
|
|
||||||
_, err = url.Parse(custodialURLBase)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = url.Parse(dataURLBase)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setConn() error {
|
func setConn() error {
|
||||||
DbConn = initializers.GetEnv("DB_CONN", "")
|
DbConn = env.GetEnv("DB_CONN", "")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadConfig initializes the configuration values after environment variables are loaded.
|
// LoadConfig initializes the configuration values after environment variables are loaded.
|
||||||
func LoadConfig() error {
|
func LoadConfig() error {
|
||||||
err := setBase()
|
err := setConn()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = setConn()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -101,15 +52,6 @@ func LoadConfig() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
CreateAccountURL, _ = url.JoinPath(custodialURLBase, createAccountPath)
|
|
||||||
TrackStatusURL, _ = url.JoinPath(custodialURLBase, trackStatusPath)
|
|
||||||
BalanceURL, _ = url.JoinPath(custodialURLBase, balancePathPrefix)
|
|
||||||
TrackURL, _ = url.JoinPath(custodialURLBase, trackPath)
|
|
||||||
TokenTransferURL, _ = url.JoinPath(custodialURLBase, tokenTransferPrefix)
|
|
||||||
VoucherHoldingsURL, _ = url.JoinPath(dataURLBase, voucherHoldingsPathPrefix)
|
|
||||||
VoucherTransfersURL, _ = url.JoinPath(dataURLBase, voucherTransfersPathPrefix)
|
|
||||||
VoucherDataURL, _ = url.JoinPath(dataURLBase, voucherDataPathPrefix)
|
|
||||||
CheckAliasURL, _ = url.JoinPath(dataURLBase, AliasPrefix)
|
|
||||||
DefaultLanguage = defaultLanguage
|
DefaultLanguage = defaultLanguage
|
||||||
Languages = languages
|
Languages = languages
|
||||||
|
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
package debug
|
|
||||||
|
|
||||||
var (
|
|
||||||
DebugCap uint32
|
|
||||||
)
|
|
84
debug/db.go
84
debug/db.go
@ -1,84 +0,0 @@
|
|||||||
package debug
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"encoding/binary"
|
|
||||||
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/common"
|
|
||||||
"git.defalsify.org/vise.git/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
dbTypStr map[common.DataTyp]string = make(map[common.DataTyp]string)
|
|
||||||
)
|
|
||||||
|
|
||||||
type KeyInfo struct {
|
|
||||||
SessionId string
|
|
||||||
Typ uint8
|
|
||||||
SubTyp common.DataTyp
|
|
||||||
Label string
|
|
||||||
Description string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k KeyInfo) String() string {
|
|
||||||
v := uint16(k.SubTyp)
|
|
||||||
s := subTypToString(k.SubTyp)
|
|
||||||
if s == "" {
|
|
||||||
v = uint16(k.Typ)
|
|
||||||
s = typToString(k.Typ)
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("Session Id: %s\nTyp: %s (%d)\n", k.SessionId, s, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ToKeyInfo(k []byte, sessionId string) (KeyInfo, error) {
|
|
||||||
o := KeyInfo{}
|
|
||||||
b := []byte(sessionId)
|
|
||||||
|
|
||||||
if len(k) <= len(b) {
|
|
||||||
return o, fmt.Errorf("storage key missing")
|
|
||||||
}
|
|
||||||
|
|
||||||
o.SessionId = sessionId
|
|
||||||
|
|
||||||
o.Typ = uint8(k[0])
|
|
||||||
k = k[1:]
|
|
||||||
o.SessionId = string(k[:len(b)])
|
|
||||||
k = k[len(b):]
|
|
||||||
|
|
||||||
if o.Typ == db.DATATYPE_USERDATA {
|
|
||||||
if len(k) == 0 {
|
|
||||||
return o, fmt.Errorf("missing subtype key")
|
|
||||||
}
|
|
||||||
v := binary.BigEndian.Uint16(k[:2])
|
|
||||||
o.SubTyp = common.DataTyp(v)
|
|
||||||
o.Label = subTypToString(o.SubTyp)
|
|
||||||
k = k[2:]
|
|
||||||
} else {
|
|
||||||
o.Label = typToString(o.Typ)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(k) != 0 {
|
|
||||||
return o, fmt.Errorf("excess key information")
|
|
||||||
}
|
|
||||||
|
|
||||||
return o, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func FromKey(k []byte) (KeyInfo, error) {
|
|
||||||
o := KeyInfo{}
|
|
||||||
|
|
||||||
if len(k) < 4 {
|
|
||||||
return o, fmt.Errorf("insufficient key length")
|
|
||||||
}
|
|
||||||
|
|
||||||
sessionIdBytes := k[1:len(k)-2]
|
|
||||||
return ToKeyInfo(k, string(sessionIdBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
func subTypToString(v common.DataTyp) string {
|
|
||||||
return dbTypStr[v + db.DATATYPE_USERDATA + 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func typToString(v uint8) string {
|
|
||||||
return dbTypStr[common.DataTyp(uint16(v))]
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
// +build debugdb
|
|
||||||
|
|
||||||
package debug
|
|
||||||
|
|
||||||
import (
|
|
||||||
"git.defalsify.org/vise.git/db"
|
|
||||||
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
DebugCap |= 1
|
|
||||||
dbTypStr[db.DATATYPE_STATE] = "internal state"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TRACKING_ID] = "tracking id"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_PUBLIC_KEY] = "public key"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACCOUNT_PIN] = "account pin"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_FIRST_NAME] = "first name"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_FAMILY_NAME] = "family name"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_YOB] = "year of birth"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_LOCATION] = "location"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_GENDER] = "gender"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_OFFERINGS] = "offerings"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_RECIPIENT] = "recipient"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_AMOUNT] = "amount"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TEMPORARY_VALUE] = "temporary value"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACTIVE_SYM] = "active sym"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACTIVE_BAL] = "active bal"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_BLOCKED_NUMBER] = "blocked number"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_PUBLIC_KEY_REVERSE] = "public_key_reverse"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACTIVE_DECIMAL] = "active decimal"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_ACTIVE_ADDRESS] = "active address"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_VOUCHER_SYMBOLS] = "voucher symbols"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_VOUCHER_BALANCES] = "voucher balances"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_VOUCHER_DECIMALS] = "voucher decimals"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_VOUCHER_ADDRESSES] = "voucher addresses"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_SENDERS] = "tx senders"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_RECIPIENTS] = "tx recipients"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_VALUES] = "tx values"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_ADDRESSES] = "tx addresses"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_HASHES] = "tx hashes"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_DATES] = "tx dates"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_SYMBOLS] = "tx symbols"
|
|
||||||
dbTypStr[db.DATATYPE_USERDATA + 1 + common.DATA_TX_DECIMALS] = "tx decimals"
|
|
||||||
}
|
|
@ -1,78 +0,0 @@
|
|||||||
package debug
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/common"
|
|
||||||
"git.defalsify.org/vise.git/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestDebugDbSubKeyInfo(t *testing.T) {
|
|
||||||
s := "foo"
|
|
||||||
b := []byte{0x20}
|
|
||||||
b = append(b, []byte(s)...)
|
|
||||||
b = append(b, []byte{0x00, 0x02}...)
|
|
||||||
r, err := ToKeyInfo(b, s)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if r.SessionId != s {
|
|
||||||
t.Fatalf("expected %s, got %s", s, r.SessionId)
|
|
||||||
}
|
|
||||||
if r.Typ != 32 {
|
|
||||||
t.Fatalf("expected 64, got %d", r.Typ)
|
|
||||||
}
|
|
||||||
if r.SubTyp != 2 {
|
|
||||||
t.Fatalf("expected 2, got %d", r.SubTyp)
|
|
||||||
}
|
|
||||||
if DebugCap & 1 > 0 {
|
|
||||||
if r.Label != "tracking id" {
|
|
||||||
t.Fatalf("expected 'tracking id', got '%s'", r.Label)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDebugDbKeyInfo(t *testing.T) {
|
|
||||||
s := "bar"
|
|
||||||
b := []byte{0x10}
|
|
||||||
b = append(b, []byte(s)...)
|
|
||||||
r, err := ToKeyInfo(b, s)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if r.SessionId != s {
|
|
||||||
t.Fatalf("expected %s, got %s", s, r.SessionId)
|
|
||||||
}
|
|
||||||
if r.Typ != 16 {
|
|
||||||
t.Fatalf("expected 16, got %d", r.Typ)
|
|
||||||
}
|
|
||||||
if DebugCap & 1 > 0 {
|
|
||||||
if r.Label != "internal state" {
|
|
||||||
t.Fatalf("expected 'internal_state', got '%s'", r.Label)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDebugDbKeyInfoRestore(t *testing.T) {
|
|
||||||
s := "bar"
|
|
||||||
b := []byte{db.DATATYPE_USERDATA}
|
|
||||||
b = append(b, []byte(s)...)
|
|
||||||
k := common.ToBytes(common.DATA_ACTIVE_SYM)
|
|
||||||
b = append(b, k...)
|
|
||||||
|
|
||||||
r, err := ToKeyInfo(b, s)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if r.SessionId != s {
|
|
||||||
t.Fatalf("expected %s, got %s", s, r.SessionId)
|
|
||||||
}
|
|
||||||
if r.Typ != 32 {
|
|
||||||
t.Fatalf("expected 32, got %d", r.Typ)
|
|
||||||
}
|
|
||||||
if DebugCap & 1 > 0 {
|
|
||||||
if r.Label != "active sym" {
|
|
||||||
t.Fatalf("expected 'active sym', got '%s'", r.Label)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
url: http://localhost:7123
|
|
||||||
dial: "*384*96#"
|
|
||||||
phoneNumber: +254722123456
|
|
@ -1,21 +0,0 @@
|
|||||||
services:
|
|
||||||
ussd-pg-store:
|
|
||||||
image: postgres:17-alpine
|
|
||||||
restart: unless-stopped
|
|
||||||
user: postgres
|
|
||||||
environment:
|
|
||||||
- POSTGRES_PASSWORD=postgres
|
|
||||||
- POSTGRES_USER=postgres
|
|
||||||
volumes:
|
|
||||||
- ./init_db.sql:/docker-entrypoint-initdb.d/init_db.sql
|
|
||||||
- ussd-pg:/var/lib/postgresql/data
|
|
||||||
ports:
|
|
||||||
- "127.0.0.1:5432:5432"
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "pg_isready"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
volumes:
|
|
||||||
ussd-pg:
|
|
||||||
driver: local
|
|
@ -1 +0,0 @@
|
|||||||
CREATE DATABASE urdt_ussd;
|
|
2
initializers/load.go → env/load.go
vendored
2
initializers/load.go → env/load.go
vendored
@ -1,4 +1,4 @@
|
|||||||
package initializers
|
package env
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
@ -1,15 +1,15 @@
|
|||||||
package errors
|
package errors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/internal/handlers"
|
"errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrInvalidRequest = handlers.ErrInvalidRequest
|
ErrInvalidRequest = errors.New("invalid request for context")
|
||||||
ErrSessionMissing = handlers.ErrSessionMissing
|
ErrSessionMissing = errors.New("missing session")
|
||||||
ErrInvalidInput = handlers.ErrInvalidInput
|
ErrInvalidInput = errors.New("invalid input")
|
||||||
ErrStorage = handlers.ErrStorage
|
ErrStorage = errors.New("storage retrieval fail")
|
||||||
ErrEngineType = handlers.ErrEngineType
|
ErrEngineType = errors.New("incompatible engine")
|
||||||
ErrEngineInit = handlers.ErrEngineInit
|
ErrEngineInit = errors.New("engine init fail")
|
||||||
ErrEngineExec = handlers.ErrEngineExec
|
ErrEngineExec = errors.New("engine exec fail")
|
||||||
)
|
)
|
||||||
|
18
go.mod
18
go.mod
@ -3,37 +3,25 @@ module git.grassecon.net/grassrootseconomics/visedriver
|
|||||||
go 1.23.0
|
go 1.23.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.defalsify.org/vise.git v0.2.3-0.20250103172917-3e190a44568d
|
git.defalsify.org/vise.git v0.2.3-0.20250114225117-3b5fc85b650b
|
||||||
github.com/alecthomas/assert/v2 v2.2.2
|
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible
|
|
||||||
github.com/grassrootseconomics/eth-custodial v1.3.0-beta
|
|
||||||
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta
|
|
||||||
github.com/jackc/pgx/v5 v5.7.1
|
github.com/jackc/pgx/v5 v5.7.1
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/peteole/testdata-loader v0.3.0
|
|
||||||
github.com/stretchr/testify v1.9.0
|
|
||||||
golang.org/x/crypto v0.27.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/repr v0.2.0 // indirect
|
|
||||||
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // indirect
|
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c // indirect
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
|
||||||
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect
|
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 // indirect
|
||||||
github.com/hexops/gotextdiff v1.0.3 // indirect
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||||
github.com/kr/text v0.2.0 // indirect
|
|
||||||
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect
|
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
github.com/stretchr/testify v1.9.0 // indirect
|
||||||
github.com/stretchr/objx v0.5.2 // indirect
|
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
|
golang.org/x/crypto v0.27.0 // indirect
|
||||||
golang.org/x/sync v0.8.0 // indirect
|
golang.org/x/sync v0.8.0 // indirect
|
||||||
golang.org/x/sys v0.25.0 // indirect
|
|
||||||
golang.org/x/text v0.18.0 // indirect
|
golang.org/x/text v0.18.0 // indirect
|
||||||
gopkg.in/leonelquinteros/gotext.v1 v1.3.1 // indirect
|
gopkg.in/leonelquinteros/gotext.v1 v1.3.1 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
|
||||||
)
|
)
|
||||||
|
31
go.sum
31
go.sum
@ -1,27 +1,14 @@
|
|||||||
git.defalsify.org/vise.git v0.2.3-0.20250103172917-3e190a44568d h1:bPAOVZOX4frSGhfOdcj7kc555f8dc9DmMd2YAyC2AMw=
|
git.defalsify.org/vise.git v0.2.3-0.20250114225117-3b5fc85b650b h1:rwWXMtNSn7aqhb4p1oVZkCA1vC7pVdohwW61QQM8fUs=
|
||||||
git.defalsify.org/vise.git v0.2.3-0.20250103172917-3e190a44568d/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
|
git.defalsify.org/vise.git v0.2.3-0.20250114225117-3b5fc85b650b/go.mod h1:jyBMe1qTYUz3mmuoC9JQ/TvFeW0vTanCUcPu3H8p4Ck=
|
||||||
github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk=
|
|
||||||
github.com/alecthomas/assert/v2 v2.2.2/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
|
|
||||||
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
|
||||||
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
|
||||||
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c h1:H9Nm+I7Cg/YVPpEV1RzU3Wq2pjamPc/UtHDgItcb7lE=
|
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c h1:H9Nm+I7Cg/YVPpEV1RzU3Wq2pjamPc/UtHDgItcb7lE=
|
||||||
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c/go.mod h1:rGod7o6KPeJ+hyBpHfhi4v7blx9sf+QsHsA7KAsdN6U=
|
github.com/barbashov/iso639-3 v0.0.0-20211020172741-1f4ffb2d8d1c/go.mod h1:rGod7o6KPeJ+hyBpHfhi4v7blx9sf+QsHsA7KAsdN6U=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
|
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
|
||||||
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
|
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
|
||||||
github.com/grassrootseconomics/eth-custodial v1.3.0-beta h1:twrMBhl89GqDUL9PlkzQxMP/6OST1BByrNDj+rqXDmU=
|
|
||||||
github.com/grassrootseconomics/eth-custodial v1.3.0-beta/go.mod h1:7uhRcdnJplX4t6GKCEFkbeDhhjlcaGJeJqevbcvGLZo=
|
|
||||||
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta h1:fn1gwbWIwHVEBtUC2zi5OqTlfI/5gU1SMk0fgGixIXk=
|
|
||||||
github.com/grassrootseconomics/ussd-data-service v1.2.0-beta/go.mod h1:omfI0QtUwIdpu9gMcUqLMCG8O1XWjqJGBx1qUMiGWC0=
|
|
||||||
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo=
|
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4 h1:U4kkNYryi/qfbBF8gh7Vsbuz+cVmhf5kt6pE9bYYyLo=
|
||||||
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y=
|
github.com/graygnuorg/go-gdbm v0.0.0-20220711140707-71387d66dce4/go.mod h1:zpZDgZFzeq9s0MIeB1P50NIEWDFFHSFBohI/NbaTD/Y=
|
||||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
|
||||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
@ -32,10 +19,6 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
|
|||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a h1:0Q3H0YXzMHiciXtRcM+j0jiCe8WKPQHoRgQiRTnfcLY=
|
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a h1:0Q3H0YXzMHiciXtRcM+j0jiCe8WKPQHoRgQiRTnfcLY=
|
||||||
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a/go.mod h1:CdTTBOYzS5E4mWS1N8NWP6AHI19MP0A2B18n3hLzRMk=
|
github.com/mattn/kinako v0.0.0-20170717041458-332c0a7e205a/go.mod h1:CdTTBOYzS5E4mWS1N8NWP6AHI19MP0A2B18n3hLzRMk=
|
||||||
github.com/pashagolub/pgxmock/v4 v4.3.0 h1:DqT7fk0OCK6H0GvqtcMsLpv8cIwWqdxWgfZNLeHCb/s=
|
github.com/pashagolub/pgxmock/v4 v4.3.0 h1:DqT7fk0OCK6H0GvqtcMsLpv8cIwWqdxWgfZNLeHCb/s=
|
||||||
@ -45,11 +28,7 @@ github.com/peteole/testdata-loader v0.3.0/go.mod h1:Mt0ZbRtb56u8SLJpNP+BnQbENljM
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
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.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
@ -60,15 +39,9 @@ golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
|||||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
|
||||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
|
||||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
|
||||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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/leonelquinteros/gotext.v1 v1.3.1 h1:8d9/fdTG0kn/B7NNGV1BsEyvektXFAbkMsTZS2sFSCc=
|
gopkg.in/leonelquinteros/gotext.v1 v1.3.1 h1:8d9/fdTG0kn/B7NNGV1BsEyvektXFAbkMsTZS2sFSCc=
|
||||||
gopkg.in/leonelquinteros/gotext.v1 v1.3.1/go.mod h1:X1WlGDeAFIYsW6GjgMm4VwUwZ2XjI7Zan2InxSUQWrU=
|
gopkg.in/leonelquinteros/gotext.v1 v1.3.1/go.mod h1:X1WlGDeAFIYsW6GjgMm4VwUwZ2XjI7Zan2InxSUQWrU=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/engine"
|
|
||||||
"git.defalsify.org/vise.git/logging"
|
|
||||||
"git.defalsify.org/vise.git/persist"
|
|
||||||
"git.defalsify.org/vise.git/resource"
|
|
||||||
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
logg = logging.NewVanilla().WithDomain("handlers")
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrInvalidRequest = errors.New("invalid request for context")
|
|
||||||
ErrSessionMissing = errors.New("missing session")
|
|
||||||
ErrInvalidInput = errors.New("invalid input")
|
|
||||||
ErrStorage = errors.New("storage retrieval fail")
|
|
||||||
ErrEngineType = errors.New("incompatible engine")
|
|
||||||
ErrEngineInit = errors.New("engine init fail")
|
|
||||||
ErrEngineExec = errors.New("engine exec fail")
|
|
||||||
)
|
|
||||||
|
|
||||||
type RequestSession struct {
|
|
||||||
Ctx context.Context
|
|
||||||
Config engine.Config
|
|
||||||
Engine engine.Engine
|
|
||||||
Input []byte
|
|
||||||
Storage *storage.Storage
|
|
||||||
Writer io.Writer
|
|
||||||
Continue bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: seems like can remove this.
|
|
||||||
type RequestParser interface {
|
|
||||||
GetSessionId(context context.Context, rq any) (string, error)
|
|
||||||
GetInput(rq any) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type RequestHandler interface {
|
|
||||||
GetConfig() engine.Config
|
|
||||||
GetRequestParser() RequestParser
|
|
||||||
GetEngine(cfg engine.Config, rs resource.Resource, pe *persist.Persister) engine.Engine
|
|
||||||
Process(rs RequestSession) (RequestSession, error)
|
|
||||||
Output(rs RequestSession) (RequestSession, error)
|
|
||||||
Reset(rs RequestSession) (RequestSession, error)
|
|
||||||
Shutdown()
|
|
||||||
}
|
|
@ -1,32 +1,26 @@
|
|||||||
package session
|
package request
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"git.defalsify.org/vise.git/db"
|
"git.defalsify.org/vise.git/db"
|
||||||
"git.defalsify.org/vise.git/engine"
|
"git.defalsify.org/vise.git/engine"
|
||||||
"git.defalsify.org/vise.git/persist"
|
"git.defalsify.org/vise.git/persist"
|
||||||
"git.defalsify.org/vise.git/resource"
|
"git.defalsify.org/vise.git/resource"
|
||||||
"git.defalsify.org/vise.git/logging"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/request"
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
"git.grassecon.net/grassrootseconomics/visedriver/storage"
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/errors"
|
"git.grassecon.net/grassrootseconomics/visedriver/errors"
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/entry"
|
"git.grassecon.net/grassrootseconomics/visedriver/entry"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type BaseRequestHandler struct {
|
||||||
logg = logging.NewVanilla().WithDomain("visedriver.session")
|
|
||||||
)
|
|
||||||
|
|
||||||
type BaseSessionHandler struct {
|
|
||||||
cfgTemplate engine.Config
|
cfgTemplate engine.Config
|
||||||
rp request.RequestParser
|
rp RequestParser
|
||||||
rs resource.Resource
|
rs resource.Resource
|
||||||
hn entry.EntryHandler
|
hn entry.EntryHandler
|
||||||
provider storage.StorageProvider
|
provider storage.StorageProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
//func NewBaseSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp request.RequestParser, hn *handlers.Handlers) *BaseSessionHandler {
|
//func NewBaseRequestHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp request.RequestParser, hn *handlers.Handlers) *BaseRequestHandler {
|
||||||
func NewBaseSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp request.RequestParser, hn entry.EntryHandler) *BaseSessionHandler {
|
func NewBaseRequestHandler(cfg engine.Config, rs resource.Resource, stateDb db.Db, userdataDb db.Db, rp RequestParser, hn entry.EntryHandler) *BaseRequestHandler {
|
||||||
return &BaseSessionHandler{
|
return &BaseRequestHandler{
|
||||||
cfgTemplate: cfg,
|
cfgTemplate: cfg,
|
||||||
rs: rs,
|
rs: rs,
|
||||||
hn: hn,
|
hn: hn,
|
||||||
@ -35,20 +29,20 @@ func NewBaseSessionHandler(cfg engine.Config, rs resource.Resource, stateDb db.D
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *BaseSessionHandler) Shutdown() {
|
func (f *BaseRequestHandler) Shutdown() {
|
||||||
err := f.provider.Close()
|
err := f.provider.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logg.Errorf("handler shutdown error", "err", err)
|
logg.Errorf("handler shutdown error", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *BaseSessionHandler) GetEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine {
|
func (f *BaseRequestHandler) GetEngine(cfg engine.Config, rs resource.Resource, pr *persist.Persister) engine.Engine {
|
||||||
en := engine.NewEngine(cfg, rs)
|
en := engine.NewEngine(cfg, rs)
|
||||||
en = en.WithPersister(pr)
|
en = en.WithPersister(pr)
|
||||||
return en
|
return en
|
||||||
}
|
}
|
||||||
|
|
||||||
func(f *BaseSessionHandler) Process(rqs request.RequestSession) (request.RequestSession, error) {
|
func(f *BaseRequestHandler) Process(rqs RequestSession) (RequestSession, error) {
|
||||||
var r bool
|
var r bool
|
||||||
var err error
|
var err error
|
||||||
var ok bool
|
var ok bool
|
||||||
@ -96,21 +90,21 @@ func(f *BaseSessionHandler) Process(rqs request.RequestSession) (request.Request
|
|||||||
return rqs, nil
|
return rqs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func(f *BaseSessionHandler) Output(rqs request.RequestSession) (request.RequestSession, error) {
|
func(f *BaseRequestHandler) Output(rqs RequestSession) (RequestSession, error) {
|
||||||
var err error
|
var err error
|
||||||
_, err = rqs.Engine.Flush(rqs.Ctx, rqs.Writer)
|
_, err = rqs.Engine.Flush(rqs.Ctx, rqs.Writer)
|
||||||
return rqs, err
|
return rqs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func(f *BaseSessionHandler) Reset(rqs request.RequestSession) (request.RequestSession, error) {
|
func(f *BaseRequestHandler) Reset(rqs RequestSession) (RequestSession, error) {
|
||||||
defer f.provider.Put(rqs.Config.SessionId, rqs.Storage)
|
defer f.provider.Put(rqs.Config.SessionId, rqs.Storage)
|
||||||
return rqs, rqs.Engine.Finish()
|
return rqs, rqs.Engine.Finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *BaseSessionHandler) GetConfig() engine.Config {
|
func (f *BaseRequestHandler) GetConfig() engine.Config {
|
||||||
return f.cfgTemplate
|
return f.cfgTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
func(f *BaseSessionHandler) GetRequestParser() request.RequestParser {
|
func(f *BaseRequestHandler) GetRequestParser() RequestParser {
|
||||||
return f.rp
|
return f.rp
|
||||||
}
|
}
|
@ -5,7 +5,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/internal/handlers"
|
"git.grassecon.net/grassrootseconomics/visedriver/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DefaultRequestParser struct {
|
type DefaultRequestParser struct {
|
||||||
@ -14,11 +14,11 @@ type DefaultRequestParser struct {
|
|||||||
func (rp *DefaultRequestParser) GetSessionId(ctx context.Context, rq any) (string, error) {
|
func (rp *DefaultRequestParser) GetSessionId(ctx context.Context, rq any) (string, error) {
|
||||||
rqv, ok := rq.(*http.Request)
|
rqv, ok := rq.(*http.Request)
|
||||||
if !ok {
|
if !ok {
|
||||||
return "", handlers.ErrInvalidRequest
|
return "", errors.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
v := rqv.Header.Get("X-Vise-Session")
|
v := rqv.Header.Get("X-Vise-Session")
|
||||||
if v == "" {
|
if v == "" {
|
||||||
return "", handlers.ErrSessionMissing
|
return "", errors.ErrSessionMissing
|
||||||
}
|
}
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
@ -26,7 +26,7 @@ func (rp *DefaultRequestParser) GetSessionId(ctx context.Context, rq any) (strin
|
|||||||
func (rp *DefaultRequestParser) GetInput(rq any) ([]byte, error) {
|
func (rp *DefaultRequestParser) GetInput(rq any) ([]byte, error) {
|
||||||
rqv, ok := rq.(*http.Request)
|
rqv, ok := rq.(*http.Request)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, handlers.ErrInvalidRequest
|
return nil, errors.ErrInvalidRequest
|
||||||
}
|
}
|
||||||
defer rqv.Body.Close()
|
defer rqv.Body.Close()
|
||||||
v, err := ioutil.ReadAll(rqv.Body)
|
v, err := ioutil.ReadAll(rqv.Body)
|
@ -13,12 +13,12 @@ var (
|
|||||||
logg = logging.NewVanilla().WithDomain("visedriver.http.session")
|
logg = logging.NewVanilla().WithDomain("visedriver.http.session")
|
||||||
)
|
)
|
||||||
|
|
||||||
// HTTPSessionHandler implements the session handler for HTTP
|
// HTTPRequestHandler implements the session handler for HTTP
|
||||||
type HTTPSessionHandler struct {
|
type HTTPRequestHandler struct {
|
||||||
request.RequestHandler
|
request.RequestHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *HTTPSessionHandler) WriteError(w http.ResponseWriter, code int, err error) {
|
func (f *HTTPRequestHandler) WriteError(w http.ResponseWriter, code int, err error) {
|
||||||
s := err.Error()
|
s := err.Error()
|
||||||
w.Header().Set("Content-Length", strconv.Itoa(len(s)))
|
w.Header().Set("Content-Length", strconv.Itoa(len(s)))
|
||||||
w.WriteHeader(code)
|
w.WriteHeader(code)
|
||||||
@ -29,13 +29,13 @@ func (f *HTTPSessionHandler) WriteError(w http.ResponseWriter, code int, err err
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHTTPSessionHandler(h request.RequestHandler) *HTTPSessionHandler {
|
func NewHTTPRequestHandler(h request.RequestHandler) *HTTPRequestHandler {
|
||||||
return &HTTPSessionHandler{
|
return &HTTPRequestHandler{
|
||||||
RequestHandler: h,
|
RequestHandler: h,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hh *HTTPSessionHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
func (hh *HTTPRequestHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
var code int
|
var code int
|
||||||
var err error
|
var err error
|
||||||
var perr error
|
var perr error
|
@ -9,7 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/engine"
|
"git.defalsify.org/vise.git/engine"
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/internal/handlers"
|
viseerrors "git.grassecon.net/grassrootseconomics/visedriver/errors"
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/testutil/mocks/httpmocks"
|
"git.grassecon.net/grassrootseconomics/visedriver/testutil/mocks/httpmocks"
|
||||||
"git.grassecon.net/grassrootseconomics/visedriver/request"
|
"git.grassecon.net/grassrootseconomics/visedriver/request"
|
||||||
)
|
)
|
||||||
@ -24,7 +24,7 @@ func (e *errorReader) Read(p []byte) (n int, err error) {
|
|||||||
return 0, errors.New("read error")
|
return 0, errors.New("read error")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionHandler_ServeHTTP(t *testing.T) {
|
func TestRequestHandler_ServeHTTP(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
sessionID string
|
sessionID string
|
||||||
@ -44,14 +44,14 @@ func TestSessionHandler_ServeHTTP(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "Missing Session ID",
|
name: "Missing Session ID",
|
||||||
sessionID: "",
|
sessionID: "",
|
||||||
parserErr: handlers.ErrSessionMissing,
|
parserErr: viseerrors.ErrSessionMissing,
|
||||||
expectedStatus: http.StatusBadRequest,
|
expectedStatus: http.StatusBadRequest,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Process Error",
|
name: "Process Error",
|
||||||
sessionID: "123",
|
sessionID: "123",
|
||||||
input: []byte("test input"),
|
input: []byte("test input"),
|
||||||
processErr: handlers.ErrStorage,
|
processErr: viseerrors.ErrStorage,
|
||||||
expectedStatus: http.StatusInternalServerError,
|
expectedStatus: http.StatusInternalServerError,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -99,7 +99,7 @@ func TestSessionHandler_ServeHTTP(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionHandler := &HTTPSessionHandler{
|
sessionHandler := &HTTPRequestHandler{
|
||||||
RequestHandler: mockRequestHandler,
|
RequestHandler: mockRequestHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,8 +118,8 @@ func TestSessionHandler_ServeHTTP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessionHandler_WriteError(t *testing.T) {
|
func TestRequestHandler_WriteError(t *testing.T) {
|
||||||
handler := &HTTPSessionHandler{}
|
handler := &HTTPRequestHandler{}
|
||||||
mockWriter := &httpmocks.MockWriter{}
|
mockWriter := &httpmocks.MockWriter{}
|
||||||
err := errors.New("test error")
|
err := errors.New("test error")
|
||||||
|
|
||||||
@ -151,13 +151,13 @@ func TestDefaultRequestParser_GetSessionId(t *testing.T) {
|
|||||||
name: "Missing Session ID",
|
name: "Missing Session ID",
|
||||||
request: httptest.NewRequest(http.MethodPost, "/", nil),
|
request: httptest.NewRequest(http.MethodPost, "/", nil),
|
||||||
expectedID: "",
|
expectedID: "",
|
||||||
expectedError: handlers.ErrSessionMissing,
|
expectedError: viseerrors.ErrSessionMissing,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invalid Request Type",
|
name: "Invalid Request Type",
|
||||||
request: invalidRequestType{},
|
request: invalidRequestType{},
|
||||||
expectedID: "",
|
expectedID: "",
|
||||||
expectedError: handlers.ErrInvalidRequest,
|
expectedError: viseerrors.ErrInvalidRequest,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +203,7 @@ func TestDefaultRequestParser_GetInput(t *testing.T) {
|
|||||||
name: "Invalid Request Type",
|
name: "Invalid Request Type",
|
||||||
request: invalidRequestType{},
|
request: invalidRequestType{},
|
||||||
expectedInput: nil,
|
expectedInput: nil,
|
||||||
expectedError: handlers.ErrInvalidRequest,
|
expectedError: viseerrors.ErrInvalidRequest,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Read Error",
|
name: "Read Error",
|
@ -1,43 +0,0 @@
|
|||||||
package storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/db"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PrefixDb interface abstracts the database operations.
|
|
||||||
type PrefixDb interface {
|
|
||||||
Get(ctx context.Context, key []byte) ([]byte, error)
|
|
||||||
Put(ctx context.Context, key []byte, val []byte) error
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ PrefixDb = (*SubPrefixDb)(nil)
|
|
||||||
|
|
||||||
type SubPrefixDb struct {
|
|
||||||
store db.Db
|
|
||||||
pfx []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSubPrefixDb(store db.Db, pfx []byte) *SubPrefixDb {
|
|
||||||
return &SubPrefixDb{
|
|
||||||
store: store,
|
|
||||||
pfx: pfx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubPrefixDb) toKey(k []byte) []byte {
|
|
||||||
return append(s.pfx, k...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubPrefixDb) Get(ctx context.Context, key []byte) ([]byte, error) {
|
|
||||||
s.store.SetPrefix(db.DATATYPE_USERDATA)
|
|
||||||
key = s.toKey(key)
|
|
||||||
return s.store.Get(ctx, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *SubPrefixDb) Put(ctx context.Context, key []byte, val []byte) error {
|
|
||||||
s.store.SetPrefix(db.DATATYPE_USERDATA)
|
|
||||||
key = s.toKey(key)
|
|
||||||
return s.store.Put(ctx, key, val)
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
package storage
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
memdb "git.defalsify.org/vise.git/db/mem"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSubPrefix(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
db := memdb.NewMemDb()
|
|
||||||
err := db.Connect(ctx, "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
sdba := NewSubPrefixDb(db, []byte("tinkywinky"))
|
|
||||||
err = sdba.Put(ctx, []byte("foo"), []byte("dipsy"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := sdba.Get(ctx, []byte("foo"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(r, []byte("dipsy")) {
|
|
||||||
t.Fatalf("expected 'dipsy', got %s", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
sdbb := NewSubPrefixDb(db, []byte("lala"))
|
|
||||||
r, err = sdbb.Get(ctx, []byte("foo"))
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("expected not found")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = sdbb.Put(ctx, []byte("foo"), []byte("pu"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
r, err = sdbb.Get(ctx, []byte("foo"))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if !bytes.Equal(r, []byte("pu")) {
|
|
||||||
t.Fatalf("expected 'pu', got %s", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err = sdba.Get(ctx, []byte("foo"))
|
|
||||||
if !bytes.Equal(r, []byte("dipsy")) {
|
|
||||||
t.Fatalf("expected 'dipsy', got %s", r)
|
|
||||||
}
|
|
||||||
}
|
|
@ -7,7 +7,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DBTYPE_MEM = iota
|
DBTYPE_NONE = iota
|
||||||
|
DBTYPE_MEM
|
||||||
|
DBTYPE_FS
|
||||||
DBTYPE_GDBM
|
DBTYPE_GDBM
|
||||||
DBTYPE_POSTGRES
|
DBTYPE_POSTGRES
|
||||||
)
|
)
|
||||||
@ -53,6 +55,19 @@ func probePostgres(s string) (string, string, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func probeGdbm(s string) (string, string, bool) {
|
func probeGdbm(s string) (string, string, bool) {
|
||||||
|
domain := "public"
|
||||||
|
v, err := url.Parse(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
if v.Scheme != "gdbm" {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
s = v.Path
|
||||||
|
return s, domain, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func probeFs(s string) (string, string, bool) {
|
||||||
if !path.IsAbs(s) {
|
if !path.IsAbs(s) {
|
||||||
return "", "", false
|
return "", "", false
|
||||||
}
|
}
|
||||||
@ -60,6 +75,13 @@ func probeGdbm(s string) (string, string, bool) {
|
|||||||
return s, "", true
|
return s, "", true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func probeMem(s string) (string, string, bool) {
|
||||||
|
if s != "" {
|
||||||
|
return "", "", false
|
||||||
|
}
|
||||||
|
return "", "", true
|
||||||
|
}
|
||||||
|
|
||||||
func ToConnData(connStr string) (ConnData, error) {
|
func ToConnData(connStr string) (ConnData, error) {
|
||||||
var o ConnData
|
var o ConnData
|
||||||
|
|
||||||
@ -82,5 +104,18 @@ func ToConnData(connStr string) (ConnData, error) {
|
|||||||
return o, nil
|
return o, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v, _, ok = probeFs(connStr)
|
||||||
|
if ok {
|
||||||
|
o.typ = DBTYPE_FS
|
||||||
|
o.str = v
|
||||||
|
return o, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v, _, ok = probeMem(connStr)
|
||||||
|
if ok {
|
||||||
|
o.typ = DBTYPE_MEM
|
||||||
|
return o, nil
|
||||||
|
}
|
||||||
|
|
||||||
return o, fmt.Errorf("invalid connection string: %s", connStr)
|
return o, fmt.Errorf("invalid connection string: %s", connStr)
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,11 @@ var (
|
|||||||
|
|
||||||
type StorageService interface {
|
type StorageService interface {
|
||||||
GetPersister(ctx context.Context) (*persist.Persister, error)
|
GetPersister(ctx context.Context) (*persist.Persister, error)
|
||||||
GetUserdataDb(ctx context.Context) db.Db
|
GetUserdataDb(ctx context.Context) (db.Db, error)
|
||||||
GetResource(ctx context.Context) (resource.Resource, error)
|
GetResource(ctx context.Context) (resource.Resource, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Support individual backend for each store (conndata)
|
||||||
type MenuStorageService struct {
|
type MenuStorageService struct {
|
||||||
conn ConnData
|
conn ConnData
|
||||||
resourceDir string
|
resourceDir string
|
||||||
@ -48,7 +49,8 @@ func (ms *MenuStorageService) WithResourceDir(resourceDir string) *MenuStorageSe
|
|||||||
return ms
|
return ms
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *MenuStorageService) getOrCreateDb(ctx context.Context, existingDb db.Db, section string) (db.Db, error) {
|
// TODO: allow fsdb, memdb
|
||||||
|
func (ms *MenuStorageService) getOrCreateDb(ctx context.Context, existingDb db.Db, section string, typ string) (db.Db, error) {
|
||||||
var newDb db.Db
|
var newDb db.Db
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
@ -56,7 +58,6 @@ func (ms *MenuStorageService) getOrCreateDb(ctx context.Context, existingDb db.D
|
|||||||
return existingDb, nil
|
return existingDb, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
connStr := ms.conn.String()
|
connStr := ms.conn.String()
|
||||||
dbTyp := ms.conn.DbType()
|
dbTyp := ms.conn.DbType()
|
||||||
if dbTyp == DBTYPE_POSTGRES {
|
if dbTyp == DBTYPE_POSTGRES {
|
||||||
@ -73,10 +74,18 @@ func (ms *MenuStorageService) getOrCreateDb(ctx context.Context, existingDb db.D
|
|||||||
}
|
}
|
||||||
connStr = path.Join(connStr, section)
|
connStr = path.Join(connStr, section)
|
||||||
newDb = gdbmstorage.NewThreadGdbmDb()
|
newDb = gdbmstorage.NewThreadGdbmDb()
|
||||||
|
} else if dbTyp == DBTYPE_FS {
|
||||||
|
err = ms.ensureDbDir()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newDb = fsdb.NewFsDb().WithBinary()
|
||||||
|
} else if dbTyp == DBTYPE_MEM {
|
||||||
|
logg.WarnCtxf(ctx, "using volatile storage (memdb)")
|
||||||
} else {
|
} else {
|
||||||
return nil, fmt.Errorf("unsupported connection string: '%s'\n", ms.conn.String())
|
return nil, fmt.Errorf("unsupported connection string: '%s'\n", ms.conn.String())
|
||||||
}
|
}
|
||||||
logg.DebugCtxf(ctx, "connecting to db", "conn", connStr, "conndata", ms.conn)
|
logg.DebugCtxf(ctx, "connecting to db", "conn", connStr, "conndata", ms.conn, "typ", typ)
|
||||||
err = newDb.Connect(ctx, connStr)
|
err = newDb.Connect(ctx, connStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -140,7 +149,7 @@ func (ms *MenuStorageService) GetUserdataDb(ctx context.Context) (db.Db, error)
|
|||||||
return ms.userDataStore, nil
|
return ms.userDataStore, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
userDataStore, err := ms.getOrCreateDb(ctx, ms.userDataStore, "userdata.gdbm")
|
userDataStore, err := ms.getOrCreateDb(ctx, ms.userDataStore, "userdata.gdbm", "userdata")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -169,7 +178,7 @@ func (ms *MenuStorageService) GetStateStore(ctx context.Context) (db.Db, error)
|
|||||||
return ms.stateStore, nil
|
return ms.stateStore, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
stateStore, err := ms.getOrCreateDb(ctx, ms.stateStore, "state.gdbm")
|
stateStore, err := ms.getOrCreateDb(ctx, ms.stateStore, "state.gdbm", "state")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
42
testutil/mocks/storage.go
Normal file
42
testutil/mocks/storage.go
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package mocks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.defalsify.org/vise.git/db"
|
||||||
|
memdb "git.defalsify.org/vise.git/db/mem"
|
||||||
|
"git.defalsify.org/vise.git/resource"
|
||||||
|
"git.defalsify.org/vise.git/persist"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MemStorageService struct {
|
||||||
|
Db db.Db
|
||||||
|
pe *persist.Persister
|
||||||
|
rs resource.Resource
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMemStorageService(ctx context.Context) *MemStorageService {
|
||||||
|
svc := &MemStorageService{
|
||||||
|
Db: memdb.NewMemDb(),
|
||||||
|
}
|
||||||
|
err := svc.Db.Connect(ctx, "")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
svc.pe = persist.NewPersister(svc.Db)
|
||||||
|
svc.rs = resource.NewMenuResource()
|
||||||
|
return svc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mss *MemStorageService) GetPersister(ctx context.Context) (*persist.Persister, error) {
|
||||||
|
return mss.pe, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mss *MemStorageService) GetUserdataDb(ctx context.Context) (db.Db, error) {
|
||||||
|
return mss.Db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mss *MemStorageService) GetResource(ctx context.Context) (resource.Resource, error) {
|
||||||
|
return mss.rs, nil
|
||||||
|
}
|
||||||
|
|
@ -1,51 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.defalsify.org/vise.git/db"
|
|
||||||
fsdb "git.defalsify.org/vise.git/db/fs"
|
|
||||||
"git.defalsify.org/vise.git/logging"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
logg = logging.NewVanilla().WithDomain("adminstore")
|
|
||||||
)
|
|
||||||
|
|
||||||
type AdminStore struct {
|
|
||||||
ctx context.Context
|
|
||||||
FsStore db.Db
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAdminStore(ctx context.Context, fileName string) (*AdminStore, error) {
|
|
||||||
fsStore, err := getFsStore(ctx, fileName)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &AdminStore{ctx: ctx, FsStore: fsStore}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getFsStore(ctx context.Context, connectStr string) (db.Db, error) {
|
|
||||||
fsStore := fsdb.NewFsDb()
|
|
||||||
err := fsStore.Connect(ctx, connectStr)
|
|
||||||
fsStore.SetPrefix(db.DATATYPE_USERDATA)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return fsStore, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if the given sessionId is listed as an admin.
|
|
||||||
func (as *AdminStore) IsAdmin(sessionId string) (bool, error) {
|
|
||||||
_, err := as.FsStore.Get(as.ctx, []byte(sessionId))
|
|
||||||
if err != nil {
|
|
||||||
if db.IsNotFound(err) {
|
|
||||||
logg.Printf(logging.LVL_INFO, "Returning false because session id was not found")
|
|
||||||
return false, nil
|
|
||||||
} else {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
56
utils/age.go
56
utils/age.go
@ -1,56 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CalculateAge calculates the age based on a given birthdate and the current date in the format dd/mm/yy
|
|
||||||
// It adjusts for cases where the current date is before the birthday in the current year.
|
|
||||||
func CalculateAge(birthdate, today time.Time) int {
|
|
||||||
today = today.In(birthdate.Location())
|
|
||||||
ty, tm, td := today.Date()
|
|
||||||
today = time.Date(ty, tm, td, 0, 0, 0, 0, time.UTC)
|
|
||||||
by, bm, bd := birthdate.Date()
|
|
||||||
birthdate = time.Date(by, bm, bd, 0, 0, 0, 0, time.UTC)
|
|
||||||
if today.Before(birthdate) {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
age := ty - by
|
|
||||||
anniversary := birthdate.AddDate(age, 0, 0)
|
|
||||||
if anniversary.After(today) {
|
|
||||||
age--
|
|
||||||
}
|
|
||||||
return age
|
|
||||||
}
|
|
||||||
|
|
||||||
// CalculateAgeWithYOB calculates the age based on the given year of birth (YOB).
|
|
||||||
// It subtracts the YOB from the current year to determine the age.
|
|
||||||
//
|
|
||||||
// Parameters:
|
|
||||||
//
|
|
||||||
// yob: The year of birth as an integer.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
//
|
|
||||||
// The calculated age as an integer.
|
|
||||||
func CalculateAgeWithYOB(yob int) int {
|
|
||||||
currentYear := time.Now().Year()
|
|
||||||
return currentYear - yob
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//IsValidYob checks if the provided yob can be considered valid
|
|
||||||
func IsValidYOb(yob string) bool {
|
|
||||||
currentYear := time.Now().Year()
|
|
||||||
yearOfBirth, err := strconv.ParseInt(yob, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if yearOfBirth >= 1900 && int(yearOfBirth) <= currentYear {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
var isoCodes = map[string]bool{
|
|
||||||
"eng": true, // English
|
|
||||||
"swa": true, // Swahili
|
|
||||||
"default": true, // Default language: English
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsValidISO639(code string) bool {
|
|
||||||
return isoCodes[code]
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
func ConstructName(firstName, familyName, defaultValue string) string {
|
|
||||||
name := defaultValue
|
|
||||||
if familyName != defaultValue {
|
|
||||||
if firstName != defaultValue {
|
|
||||||
name = firstName + " " + familyName
|
|
||||||
} else {
|
|
||||||
name = familyName
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if firstName != defaultValue {
|
|
||||||
name = firstName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return name
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user