Compare commits

..

51 Commits

Author SHA1 Message Date
Carlosokumu
345dfbaa21 Merge remote-tracking branch 'refs/remotes/origin/profile-edit-show' into profile-edit-show 2024-11-14 11:01:42 +03:00
Carlosokumu
381e581e8e add terminal logs 2024-11-14 11:00:12 +03:00
Carlosokumu
94d2e8203f reload verify yob 2024-11-14 10:53:20 +03:00
Carlosokumu
f9e51618c5 remove extra line 2024-11-14 10:29:22 +03:00
Carlosokumu
f97ad2a262 use 1 for retry 2024-11-14 10:25:42 +03:00
alfred-mk
59b14301ad Merge branch 'master' into profile-edit-show 2024-11-14 10:08:25 +03:00
8b097a4395 Merge pull request 'log directly to the terminal' (#170) from terminal-logs into master
Reviewed-on: #170
2024-11-14 08:01:59 +01:00
Carlosokumu
fd2486b5cf Merge branch 'terminal-logs' into profile-edit-show 2024-11-13 21:04:40 +03:00
alfred-mk
f9f25d898b use logg 2024-11-13 19:00:27 +03:00
alfred-mk
b6b3ef83a4 updated the comment to match the functionality 2024-11-13 18:02:02 +03:00
alfred-mk
d7232a53ef use the bearer token 2024-11-13 16:16:32 +03:00
alfred-mk
047bf0e12e updated the env example 2024-11-13 16:15:37 +03:00
alfred-mk
09f61eb64d log all errors from the hander 2024-11-13 15:19:45 +03:00
Carlosokumu
abdb17640b add terminal logs 2024-11-12 10:36:08 +03:00
alfred-mk
9af7b775a7 log directly to the terminal 2024-11-11 16:32:17 +03:00
Carlosokumu
97741b113b Merge branch 'master' into profile-edit-show 2024-11-11 11:03:01 +03:00
0bb444cd50 Merge pull request 'updated README' (#169) from readme-documentation into master
Reviewed-on: #169
2024-11-08 22:23:57 +01:00
alfred-mk
1d07d7fb1d updated README 2024-11-08 20:35:17 +03:00
a3e5aab6c4 Merge pull request 'Africastalking POST route' (#168) from africastalking-endpoint into master
Reviewed-on: #168
2024-11-08 17:25:46 +01:00
Carlosokumu
9ebfb643aa remove unused import 2024-11-08 17:28:20 +03:00
Carlosokumu
0aad21a52c Merge branch 'master' into profile-edit-show 2024-11-08 17:21:27 +03:00
Carlosokumu
68d1628546 replace - with: Not provided 2024-11-08 17:19:41 +03:00
Carlosokumu
f4f95b3292 add some spacing 2024-11-08 17:15:27 +03:00
alfred-mk
574807d254 set the africastalking POST route using env 2024-11-08 16:52:19 +03:00
carlos
256ed6491b Merge pull request 'http-logs' (#167) from http-logs into master
Reviewed-on: #167
2024-11-08 12:51:30 +01:00
carlos
7a02ffcf0c Merge pull request 'log-file' (#166) from log-file into http-logs
Reviewed-on: #166
2024-11-08 08:09:15 +01:00
Carlosokumu
dcd8fce59a add log on create account 2024-11-08 10:07:06 +03:00
alfred-mk
64a7b49218 Remove unused Warning logger 2024-11-08 00:21:11 +03:00
alfred-mk
1bcbb2079e Removed unused variable 2024-11-08 00:17:02 +03:00
alfred-mk
e63468433e Merge branch 'http-logs' into log-file 2024-11-08 00:14:34 +03:00
alfred-mk
9c972ffa6b add specific log files per binary 2024-11-07 16:46:12 +03:00
alfred-mk
a11776e1b3 ignore .log files 2024-11-07 16:41:38 +03:00
Carlosokumu
6ac9ac29d8 add simple http logging 2024-11-07 16:41:08 +03:00
Carlosokumu
fc8915ea33 add http logging 2024-11-07 16:35:58 +03:00
Carlosokumu
cc36ddcb6d add handler for showing the currently set profile information 2024-11-07 12:02:03 +03:00
Carlosokumu
f3388aef31 use _ for back 2024-11-07 11:59:12 +03:00
Carlosokumu
4e170b25e2 show currently set profile information 2024-11-07 11:58:58 +03:00
Carlosokumu
3e258a35fa show currently set profile information 2024-11-07 11:58:25 +03:00
Carlosokumu
f66609bbae add helper for getting db key from string 2024-11-07 09:18:11 +03:00
Carlosokumu
ebdc7b200a register handler for getting current profile information 2024-11-07 09:16:49 +03:00
Carlosokumu
29e1e912d7 use edit prefix on each profile edit node 2024-11-06 17:52:22 +03:00
Carlosokumu
308f3327d0 use edit prefix on each profile edit information 2024-11-06 17:48:50 +03:00
46b2b354fd Merge pull request 'swahili-templates-menu' (#158) from swahili-templates-menu into master
Reviewed-on: #158
Reviewed-by: Alfred Kamanda <alfredkamandamw@gmail.com>
2024-11-05 11:29:07 +01:00
Carlosokumu
7676cfd40c Merge branch 'master' into swahili-templates-menu 2024-11-05 13:25:49 +03:00
Carlosokumu
4e350aa25a update test data file 2024-11-05 10:49:08 +03:00
859de0513a Merge pull request 'api-error-fix' (#161) from api-error-fix into master
Reviewed-on: #161
2024-11-05 00:18:04 +01:00
Carlosokumu
c46c31ea36 ensure swahili translation 2024-11-04 16:06:33 +03:00
Carlosokumu
da91eed9d4 add retry menu option 2024-11-04 15:55:58 +03:00
Carlosokumu
2b865a365b add back option to view address 2024-11-04 15:24:51 +03:00
Carlosokumu
c77558689a ensure swahlili menu template 2024-11-04 15:24:22 +03:00
Carlosokumu
a9641fd70d ensure swahili menu template 2024-11-04 15:24:01 +03:00
61 changed files with 426 additions and 94 deletions

View File

@@ -2,6 +2,9 @@
PORT=7123
HOST=127.0.0.1
#AfricasTalking USSD POST endpoint
AT_ENDPOINT=/ussd/africastalking
#PostgreSQL
DB_HOST=localhost
DB_USER=postgres
@@ -12,7 +15,7 @@ DB_SSLMODE=disable
DB_TIMEZONE=Africa/Nairobi
#External API Calls
CREATE_ACCOUNT_URL=http://localhost:5003/api/v2/account/create
TRACK_STATUS_URL=https://custodial.sarafu.africa/api/track/
BALANCE_URL=https://custodial.sarafu.africa/api/account/status/
TRACK_URL=http://localhost:5003/api/v2/account/status
CUSTODIAL_URL_BASE=http://localhost:5003
CUSTODIAL_BEARER_TOKEN=eyJeSIsInRcCI6IkpXVCJ.yJwdWJsaWNLZXkiOiIwrrrrrr
DATA_URL_BASE=http://localhost:5006
DATA_BEARER_TOKEN=eyJeSIsIRcCI6IXVCJ.yJwdWJsaLZXkiOiIwrrrrrr

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ go.work*
cmd/.state/
id_*
*.gdbm
*.log

View File

@@ -1,8 +1,91 @@
# ussd
# URDT USSD service
> USSD
This is a USSD service built using the [go-vise](https://github.com/nolash/go-vise) engine.
USSD service.
## 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

View File

@@ -1,9 +1,13 @@
package main
import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"os/signal"
@@ -25,8 +29,8 @@ import (
)
var (
logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration")
logg = logging.NewVanilla()
scriptDir = path.Join("services", "registration")
)
func init() {
@@ -38,9 +42,30 @@ type atRequestParser struct{}
func (arp *atRequestParser) GetSessionId(rq any) (string, error) {
rqv, ok := rq.(*http.Request)
if !ok {
log.Println("got an invalid request:", rq)
return "", handlers.ErrInvalidRequest
}
// Capture body (if any) for logging
body, err := io.ReadAll(rqv.Body)
if err != nil {
log.Println("failed to read request body:", err)
return "", fmt.Errorf("failed to read request body: %v", err)
}
// Reset the body for further reading
rqv.Body = io.NopCloser(bytes.NewReader(body))
// Log the body as JSON
bodyLog := map[string]string{"body": string(body)}
logBytes, err := json.Marshal(bodyLog)
if err != nil {
log.Println("failed to marshal request body:", err)
} else {
log.Println("Received request:", string(logBytes))
}
if err := rqv.ParseForm(); err != nil {
log.Println("failed to parse form data: %v", err)
return "", fmt.Errorf("failed to parse form data: %v", err)
}
@@ -156,9 +181,13 @@ func main() {
rp := &atRequestParser{}
bsh := handlers.NewBaseSessionHandler(cfg, rs, stateStore, userdataStore, rp, hl)
sh := httpserver.NewATSessionHandler(bsh)
mux := http.NewServeMux()
mux.Handle(initializers.GetEnv("AT_ENDPOINT", "/"), sh)
s := &http.Server{
Addr: fmt.Sprintf("%s:%s", host, strconv.Itoa(int(port))),
Handler: sh,
Handler: mux,
}
s.RegisterOnShutdown(sh.Shutdown)

View File

@@ -2,6 +2,7 @@ package common
import (
"encoding/binary"
"errors"
"git.defalsify.org/vise.git/logging"
)
@@ -48,3 +49,23 @@ 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")
}
}

View File

@@ -135,7 +135,7 @@ func GetTemporaryVoucherData(ctx context.Context, store DataStore, sessionId str
return data, nil
}
// UpdateVoucherData sets the active voucher data and clears the temporary voucher data in the DataStore.
// UpdateVoucherData sets 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

View File

@@ -7,30 +7,30 @@ import (
)
const (
createAccountPath = "/api/v2/account/create"
trackStatusPath = "/api/track"
balancePathPrefix = "/api/account"
trackPath = "/api/v2/account/status"
voucherHoldingsPathPrefix = "/api/v1/holdings"
createAccountPath = "/api/v2/account/create"
trackStatusPath = "/api/track"
balancePathPrefix = "/api/account"
trackPath = "/api/v2/account/status"
voucherHoldingsPathPrefix = "/api/v1/holdings"
voucherTransfersPathPrefix = "/api/v1/transfers/last10"
voucherDataPathPrefix = "/api/v1/token"
voucherDataPathPrefix = "/api/v1/token"
)
var (
custodialURLBase string
dataURLBase string
CustodialAPIKey string
DataAPIKey string
custodialURLBase string
dataURLBase string
CustodialBearerToken string
DataBearerToken string
)
var (
CreateAccountURL string
TrackStatusURL string
BalanceURL string
TrackURL string
VoucherHoldingsURL string
VoucherTransfersURL string
VoucherDataURL string
CreateAccountURL string
TrackStatusURL string
BalanceURL string
TrackURL string
VoucherHoldingsURL string
VoucherTransfersURL string
VoucherDataURL string
)
func setBase() error {
@@ -38,8 +38,8 @@ func setBase() error {
custodialURLBase = initializers.GetEnv("CUSTODIAL_URL_BASE", "http://localhost:5003")
dataURLBase = initializers.GetEnv("DATA_URL_BASE", "http://localhost:5006")
CustodialAPIKey = initializers.GetEnv("CUSTODIAL_API_KEY", "xd")
DataAPIKey = initializers.GetEnv("DATA_API_KEY", "xd")
CustodialBearerToken = initializers.GetEnv("CUSTODIAL_BEARER_TOKEN", "")
DataBearerToken = initializers.GetEnv("DATA_BEARER_TOKEN", "")
_, err = url.JoinPath(custodialURLBase, "/foo")
if err != nil {
@@ -58,7 +58,7 @@ func LoadConfig() error {
if err != nil {
return err
}
CreateAccountURL, _ = url.JoinPath(custodialURLBase, createAccountPath)
CreateAccountURL, _ = url.JoinPath(custodialURLBase, createAccountPath)
TrackStatusURL, _ = url.JoinPath(custodialURLBase, trackStatusPath)
BalanceURL, _ = url.JoinPath(custodialURLBase, balancePathPrefix)
TrackURL, _ = url.JoinPath(custodialURLBase, trackPath)

View File

@@ -115,6 +115,7 @@ func (ls *LocalHandlerService) GetHandler(accountService remote.AccountServiceIn
ls.DbRs.AddLocalFunc("reset_unregistered_number", ussdHandlers.ResetUnregisteredNumber)
ls.DbRs.AddLocalFunc("reset_others_pin", ussdHandlers.ResetOthersPin)
ls.DbRs.AddLocalFunc("save_others_temporary_pin", ussdHandlers.SaveOthersTemporaryPin)
ls.DbRs.AddLocalFunc("get_current_profile_info", ussdHandlers.GetCurrentProfileInfo)
return ussdHandlers, nil
}

View File

@@ -161,6 +161,7 @@ func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (r
languageSetFlag, err := h.flagManager.GetFlag("flag_language_set")
if err != nil {
logg.ErrorCtxf(ctx, "Error setting the languageSetFlag", "error", err)
return res, err
}
res.FlagSet = append(res.FlagSet, languageSetFlag)
@@ -198,7 +199,6 @@ func (h *Handlers) createAccountNoExist(ctx context.Context, sessionId string, r
}
res.FlagSet = append(res.FlagSet, flag_account_created)
return nil
}
// CreateAccount checks if any account exists on the JSON data file, and if not
@@ -215,13 +215,15 @@ func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte)
_, err = store.ReadEntry(ctx, sessionId, common.DATA_ACCOUNT_CREATED)
if err != nil {
if db.IsNotFound(err) {
logg.Printf(logging.LVL_INFO, "Creating an account because it doesn't exist")
logg.InfoCtxf(ctx, "Creating an account because it doesn't exist")
err = h.createAccountNoExist(ctx, sessionId, &res)
if err != nil {
logg.ErrorCtxf(ctx, "failed on createAccountNoExist", "error", err)
return res, err
}
}
}
return res, nil
}
@@ -235,10 +237,12 @@ func (h *Handlers) CheckPinMisMatch(ctx context.Context, sym string, input []byt
store := h.userdataStore
blockedNumber, err := store.ReadEntry(ctx, sessionId, common.DATA_BLOCKED_NUMBER)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read blockedNumber entry with", "key", common.DATA_BLOCKED_NUMBER, "error", err)
return res, err
}
temporaryPin, err := store.ReadEntry(ctx, string(blockedNumber), common.DATA_TEMPORARY_VALUE)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read temporaryPin entry with", "key", common.DATA_TEMPORARY_VALUE, "error", err)
return res, err
}
if bytes.Equal(temporaryPin, input) {
@@ -291,6 +295,7 @@ func (h *Handlers) SaveTemporaryPin(ctx context.Context, sym string, input []byt
store := h.userdataStore
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(accountPIN))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write temporaryAccountPIN entry with", "key", common.DATA_TEMPORARY_VALUE, "value", accountPIN, "error", err)
return res, err
}
@@ -308,12 +313,14 @@ func (h *Handlers) SaveOthersTemporaryPin(ctx context.Context, sym string, input
}
temporaryPin := string(input)
blockedNumber, err := store.ReadEntry(ctx, sessionId, common.DATA_BLOCKED_NUMBER)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read blockedNumber entry with", "key", common.DATA_BLOCKED_NUMBER, "error", err)
return res, err
}
err = store.WriteEntry(ctx, string(blockedNumber), common.DATA_TEMPORARY_VALUE, []byte(temporaryPin))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write temporaryPin entry with", "key", common.DATA_TEMPORARY_VALUE, "value", temporaryPin, "error", err)
return res, err
}
@@ -331,6 +338,7 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt
store := h.userdataStore
temporaryPin, err := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read temporaryPin entry with", "key", common.DATA_TEMPORARY_VALUE, "error", err)
return res, err
}
if bytes.Equal(temporaryPin, input) {
@@ -340,6 +348,7 @@ func (h *Handlers) ConfirmPinChange(ctx context.Context, sym string, input []byt
}
err = store.WriteEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN, []byte(temporaryPin))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write temporaryPin entry with", "key", common.DATA_ACCOUNT_PIN, "value", temporaryPin, "error", err)
return res, err
}
return res, nil
@@ -362,6 +371,7 @@ func (h *Handlers) VerifyCreatePin(ctx context.Context, sym string, input []byte
store := h.userdataStore
temporaryPin, err := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read temporaryPin entry with", "key", common.DATA_TEMPORARY_VALUE, "error", err)
return res, err
}
if bytes.Equal(input, temporaryPin) {
@@ -374,6 +384,7 @@ func (h *Handlers) VerifyCreatePin(ctx context.Context, sym string, input []byte
err = store.WriteEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN, []byte(temporaryPin))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write temporaryPin entry with", "key", common.DATA_ACCOUNT_PIN, "value", temporaryPin, "error", err)
return res, err
}
@@ -406,11 +417,13 @@ func (h *Handlers) SaveFirstname(ctx context.Context, sym string, input []byte)
temporaryFirstName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, common.DATA_FIRST_NAME, []byte(temporaryFirstName))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write firstName entry with", "key", common.DATA_FIRST_NAME, "value", temporaryFirstName, "error", err)
return res, err
}
} else {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(firstName))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write temporaryFirstName entry with", "key", common.DATA_TEMPORARY_VALUE, "value", firstName, "error", err)
return res, err
}
}
@@ -437,14 +450,17 @@ func (h *Handlers) SaveFamilyname(ctx context.Context, sym string, input []byte)
temporaryFamilyName, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, common.DATA_FAMILY_NAME, []byte(temporaryFamilyName))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write familyName entry with", "key", common.DATA_FAMILY_NAME, "value", temporaryFamilyName, "error", err)
return res, err
}
} else {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(familyName))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write temporaryFamilyName entry with", "key", common.DATA_TEMPORARY_VALUE, "value", familyName, "error", err)
return res, err
}
}
return res, nil
}
@@ -465,11 +481,13 @@ func (h *Handlers) SaveYob(ctx context.Context, sym string, input []byte) (resou
temporaryYob, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, common.DATA_YOB, []byte(temporaryYob))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write yob entry with", "key", common.DATA_TEMPORARY_VALUE, "value", temporaryYob, "error", err)
return res, err
}
} else {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(yob))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write temporaryYob entry with", "key", common.DATA_TEMPORARY_VALUE, "value", yob, "error", err)
return res, err
}
}
@@ -495,11 +513,13 @@ func (h *Handlers) SaveLocation(ctx context.Context, sym string, input []byte) (
temporaryLocation, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, common.DATA_LOCATION, []byte(temporaryLocation))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write location entry with", "key", common.DATA_LOCATION, "value", temporaryLocation, "error", err)
return res, err
}
} else {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(location))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write temporaryLocation entry with", "key", common.DATA_TEMPORARY_VALUE, "value", location, "error", err)
return res, err
}
}
@@ -525,11 +545,13 @@ func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (re
temporaryGender, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, common.DATA_GENDER, []byte(temporaryGender))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write gender entry with", "key", common.DATA_GENDER, "value", gender, "error", err)
return res, err
}
} else {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(gender))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write temporaryGender entry with", "key", common.DATA_TEMPORARY_VALUE, "value", gender, "error", err)
return res, err
}
}
@@ -556,11 +578,13 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
temporaryOfferings, _ := store.ReadEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE)
err = store.WriteEntry(ctx, sessionId, common.DATA_OFFERINGS, []byte(temporaryOfferings))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write offerings entry with", "key", common.DATA_TEMPORARY_VALUE, "value", offerings, "error", err)
return res, err
}
} else {
err = store.WriteEntry(ctx, sessionId, common.DATA_TEMPORARY_VALUE, []byte(offerings))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write temporaryOfferings entry with", "key", common.DATA_TEMPORARY_VALUE, "value", offerings, "error", err)
return res, err
}
}
@@ -571,9 +595,7 @@ func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte)
// ResetAllowUpdate resets the allowupdate flag that allows a user to update profile data.
func (h *Handlers) ResetAllowUpdate(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
flag_allow_update, _ := h.flagManager.GetFlag("flag_allow_update")
res.FlagReset = append(res.FlagReset, flag_allow_update)
return res, nil
}
@@ -590,7 +612,6 @@ func (h *Handlers) ResetValidPin(ctx context.Context, sym string, input []byte)
func (h *Handlers) ResetAccountAuthorized(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
res.FlagReset = append(res.FlagReset, flag_account_authorized)
return res, nil
}
@@ -626,6 +647,7 @@ func (h *Handlers) Authorize(ctx context.Context, sym string, input []byte) (res
store := h.userdataStore
AccountPin, err := store.ReadEntry(ctx, sessionId, common.DATA_ACCOUNT_PIN)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read AccountPin entry with", "key", common.DATA_ACCOUNT_PIN, "error", err)
return res, err
}
if len(input) == 4 {
@@ -673,18 +695,19 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b
store := h.userdataStore
publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
return res, err
}
r, err := h.accountService.TrackAccountStatus(ctx, string(publicKey))
r, err := h.accountService.TrackAccountStatus(ctx, string(publicKey))
if err != nil {
res.FlagSet = append(res.FlagSet, flag_api_error)
logg.ErrorCtxf(ctx, "failed on TrackAccountStatus", err)
return res, err
}
res.FlagReset = append(res.FlagReset, flag_api_error)
if !ok {
return res, err
}
if r.Active {
res.FlagSet = append(res.FlagSet, flag_account_success)
res.FlagReset = append(res.FlagReset, flag_account_pending)
@@ -692,6 +715,7 @@ func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []b
res.FlagReset = append(res.FlagReset, flag_account_success)
res.FlagSet = append(res.FlagSet, flag_account_pending)
}
return res, nil
}
@@ -783,11 +807,13 @@ func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (
return res, nil
}
logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", common.DATA_ACTIVE_SYM, "error", err)
return res, err
}
activeBal, err := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_BAL)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", common.DATA_ACTIVE_BAL, "error", err)
return res, err
}
@@ -811,6 +837,7 @@ func (h *Handlers) FetchCustodialBalances(ctx context.Context, sym string, input
store := h.userdataStore
publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
return res, err
}
@@ -843,16 +870,19 @@ func (h *Handlers) ResetOthersPin(ctx context.Context, sym string, input []byte)
}
blockedPhonenumber, err := store.ReadEntry(ctx, sessionId, common.DATA_BLOCKED_NUMBER)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read blockedPhonenumber entry with", "key", common.DATA_BLOCKED_NUMBER, "error", err)
return res, err
}
temporaryPin, err := store.ReadEntry(ctx, string(blockedPhonenumber), common.DATA_TEMPORARY_VALUE)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read temporaryPin entry with", "key", common.DATA_TEMPORARY_VALUE, "error", err)
return res, err
}
err = store.WriteEntry(ctx, string(blockedPhonenumber), common.DATA_ACCOUNT_PIN, []byte(temporaryPin))
if err != nil {
return res, nil
}
return res, nil
}
@@ -881,10 +911,11 @@ func (h *Handlers) ValidateBlockedNumber(ctx context.Context, sym string, input
}
if err != nil {
if db.IsNotFound(err) {
logg.Printf(logging.LVL_INFO, "Invalid or unregistered number")
logg.InfoCtxf(ctx, "Invalid or unregistered number")
res.FlagSet = append(res.FlagSet, flag_unregistered_number)
return res, nil
} else {
logg.ErrorCtxf(ctx, "Error on ValidateBlockedNumber", "error", err)
return res, err
}
}
@@ -992,6 +1023,7 @@ func (h *Handlers) MaxAmount(ctx context.Context, sym string, input []byte) (res
activeBal, err := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_BAL)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", common.DATA_ACTIVE_BAL, "error", err)
return res, err
}
@@ -1017,10 +1049,12 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
// retrieve the active balance
activeBal, err := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_BAL)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read activeBal entry with", "key", common.DATA_ACTIVE_BAL, "error", err)
return res, err
}
balanceValue, err = strconv.ParseFloat(string(activeBal), 64)
if err != nil {
logg.ErrorCtxf(ctx, "Failed to convert the activeBal to a float", "error", err)
return res, err
}
@@ -1043,10 +1077,11 @@ func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte)
formattedAmount := fmt.Sprintf("%.2f", inputAmount)
err = store.WriteEntry(ctx, sessionId, common.DATA_AMOUNT, []byte(formattedAmount))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write amount entry with", "key", common.DATA_AMOUNT, "value", formattedAmount, "error", err)
return res, err
}
res.Content = fmt.Sprintf("%s", formattedAmount)
res.Content = formattedAmount
return res, nil
}
@@ -1109,6 +1144,7 @@ func (h *Handlers) GetAmount(ctx context.Context, sym string, input []byte) (res
// retrieve the active symbol
activeSym, err := store.ReadEntry(ctx, sessionId, common.DATA_ACTIVE_SYM)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", common.DATA_ACTIVE_SYM, "error", err)
return res, err
}
@@ -1146,6 +1182,7 @@ func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input []
account_authorized_flag, err := h.flagManager.GetFlag("flag_account_authorized")
if err != nil {
logg.ErrorCtxf(ctx, "Failed to set the flag_account_authorized", "error", err)
return res, err
}
@@ -1153,6 +1190,101 @@ func (h *Handlers) InitiateTransaction(ctx context.Context, sym string, input []
return res, nil
}
func (h *Handlers) GetCurrentProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
var profileInfo []byte
var err error
sessionId, ok := ctx.Value("SessionId").(string)
if !ok {
return res, fmt.Errorf("missing session")
}
sm, _ := h.st.Where()
parts := strings.SplitN(sm, "_", 2)
filename := parts[1]
dbKeyStr := "DATA_" + strings.ToUpper(filename)
dbKey, err := common.StringToDataTyp(dbKeyStr)
if err != nil {
return res, err
}
store := h.userdataStore
switch dbKey {
case common.DATA_FIRST_NAME:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_FIRST_NAME)
if err != nil {
if db.IsNotFound(err) {
res.Content = "Not provided"
break
}
logg.ErrorCtxf(ctx, "Failed to read first name entry with", "key", "error", common.DATA_FIRST_NAME, err)
return res, err
}
res.Content = string(profileInfo)
case common.DATA_FAMILY_NAME:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_FAMILY_NAME)
if err != nil {
if db.IsNotFound(err) {
res.Content = "Not provided"
break
}
logg.ErrorCtxf(ctx, "Failed to read family name entry with", "key", "error", common.DATA_FAMILY_NAME, err)
return res, err
}
res.Content = string(profileInfo)
case common.DATA_GENDER:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_GENDER)
if err != nil {
if db.IsNotFound(err) {
res.Content = "Not provided"
break
}
logg.ErrorCtxf(ctx, "Failed to read gender entry with", "key", "error", common.DATA_GENDER, err)
return res, err
}
res.Content = string(profileInfo)
case common.DATA_YOB:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_YOB)
if err != nil {
if db.IsNotFound(err) {
res.Content = "Not provided"
break
}
logg.ErrorCtxf(ctx, "Failed to read year of birth(yob) entry with", "key", "error", common.DATA_YOB, err)
return res, err
}
res.Content = string(profileInfo)
case common.DATA_LOCATION:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_LOCATION)
if err != nil {
if db.IsNotFound(err) {
res.Content = "Not provided"
break
}
logg.ErrorCtxf(ctx, "Failed to read location entry with", "key", "error", common.DATA_LOCATION, err)
return res, err
}
res.Content = string(profileInfo)
case common.DATA_OFFERINGS:
profileInfo, err = store.ReadEntry(ctx, sessionId, common.DATA_OFFERINGS)
if err != nil {
if db.IsNotFound(err) {
res.Content = "Not provided"
break
}
logg.ErrorCtxf(ctx, "Failed to read offerings entry with", "key", "error", common.DATA_OFFERINGS, err)
return res, err
}
res.Content = string(profileInfo)
default:
break
}
return res, nil
}
func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) {
var res resource.Result
var defaultValue string
@@ -1248,6 +1380,7 @@ func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []by
if db.IsNotFound(err) {
publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
if err != nil {
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
return res, err
}
@@ -1272,17 +1405,20 @@ func (h *Handlers) SetDefaultVoucher(ctx context.Context, sym string, input []by
// set the active symbol
err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_SYM, []byte(defaultSym))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write defaultSym entry with", "key", common.DATA_ACTIVE_SYM, "value", defaultSym, "error", err)
return res, err
}
// set the active balance
err = store.WriteEntry(ctx, sessionId, common.DATA_ACTIVE_BAL, []byte(defaultBal))
if err != nil {
logg.ErrorCtxf(ctx, "failed to write defaultBal entry with", "key", common.DATA_ACTIVE_BAL, "value", defaultBal, "error", err)
return res, err
}
return res, nil
}
logg.ErrorCtxf(ctx, "failed to read activeSym entry with", "key", common.DATA_ACTIVE_SYM, "error", err)
return res, err
}
@@ -1303,7 +1439,8 @@ func (h *Handlers) CheckVouchers(ctx context.Context, sym string, input []byte)
store := h.userdataStore
publicKey, err := store.ReadEntry(ctx, sessionId, common.DATA_PUBLIC_KEY)
if err != nil {
return res, nil
logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", common.DATA_PUBLIC_KEY, "error", err)
return res, err
}
// Fetch vouchers from the API using the public key
@@ -1338,6 +1475,7 @@ func (h *Handlers) GetVoucherList(ctx context.Context, sym string, input []byte)
// Read vouchers from the store
voucherData, err := h.prefixDb.Get(ctx, []byte("sym"))
if err != nil {
logg.ErrorCtxf(ctx, "Failed to read the voucherData from prefixDb", "error", err)
return res, err
}
@@ -1373,6 +1511,7 @@ func (h *Handlers) ViewVoucher(ctx context.Context, sym string, input []byte) (r
}
if err := common.StoreTemporaryVoucher(ctx, h.userdataStore, sessionId, metadata); err != nil {
logg.ErrorCtxf(ctx, "failed on StoreTemporaryVoucher", "error", err)
return res, err
}
@@ -1394,11 +1533,13 @@ func (h *Handlers) SetVoucher(ctx context.Context, sym string, input []byte) (re
// Get temporary data
tempData, err := common.GetTemporaryVoucherData(ctx, h.userdataStore, sessionId)
if err != nil {
logg.ErrorCtxf(ctx, "failed on GetTemporaryVoucherData", "error", err)
return res, err
}
// Set as active and clear temporary data
if err := common.UpdateVoucherData(ctx, h.userdataStore, sessionId, tempData); err != nil {
logg.ErrorCtxf(ctx, "failed on UpdateVoucherData", "error", err)
return res, err
}

View File

@@ -54,7 +54,7 @@
},
{
"input": "1235",
"expectedContent": "Incorrect pin\n1:retry\n9:Quit"
"expectedContent": "Incorrect pin\n1:Retry\n9:Quit"
},
{
"input": "1",
@@ -95,7 +95,7 @@
},
{
"input": "1235",
"expectedContent": "Incorrect pin\n1:retry\n9:Quit"
"expectedContent": "Incorrect pin\n1:Retry\n9:Quit"
},
{
"input": "1",
@@ -141,7 +141,7 @@
},
{
"input": "1235",
"expectedContent": "Incorrect pin\n1:retry\n9:Quit"
"expectedContent": "Incorrect pin\n1:Retry\n9:Quit"
},
{
"input": "1",

View File

@@ -23,7 +23,7 @@
},
{
"input": "1111",
"expectedContent": "The PIN is not a match. Try again\n1:retry\n9:Quit"
"expectedContent": "The PIN is not a match. Try again\n1:Retry\n9:Quit"
},
{
"input": "1",
@@ -65,7 +65,7 @@
},
{
"input": "000",
"expectedContent": "000 is not registered or invalid, please try again:\n1:retry\n9:Quit"
"expectedContent": "000 is not registered or invalid, please try again:\n1:Retry\n9:Quit"
},
{
"input": "1",
@@ -77,7 +77,7 @@
},
{
"input": "10000000",
"expectedContent": "Amount 10000000 is invalid, please try again:\n1:retry\n9:Quit"
"expectedContent": "Amount 10000000 is invalid, please try again:\n1:Retry\n9:Quit"
},
{
"input": "1",
@@ -89,7 +89,7 @@
},
{
"input": "1222",
"expectedContent": "Incorrect pin\n1:retry\n9:Quit"
"expectedContent": "Incorrect pin\n1:Retry\n9:Quit"
},
{
"input": "1",
@@ -140,7 +140,7 @@
},
{
"input": "6",
"expectedContent": "Address: {public_key}\n9:Quit"
"expectedContent": "Address: {public_key}\n0:Back\n9:Quit"
},
{
"input": "9",

View File

@@ -1,20 +1,19 @@
package remote
import (
"bytes"
"context"
"encoding/json"
"errors"
"io"
"log"
"net/http"
"net/url"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
"github.com/grassrootseconomics/eth-custodial/pkg/api"
"git.grassecon.net/urdt/ussd/config"
"git.grassecon.net/urdt/ussd/models"
)
var (
"github.com/grassrootseconomics/eth-custodial/pkg/api"
dataserviceapi "github.com/grassrootseconomics/ussd-data-service/pkg/api"
)
type AccountServiceInterface interface {
@@ -51,7 +50,7 @@ func (as *AccountService) TrackAccountStatus(ctx context.Context, publicKey stri
return nil, err
}
_, err = doCustodialRequest(ctx, req, &r)
_, err = doCustodialRequest(ctx, req, &r)
if err != nil {
return nil, err
}
@@ -79,7 +78,6 @@ func (as *AccountService) CheckBalance(ctx context.Context, publicKey string) (*
return &balanceResult, err
}
// CreateAccount creates a new account in the custodial system.
// Returns:
// - *models.AccountResponse: A pointer to an AccountResponse struct containing the details of the created account.
@@ -93,8 +91,7 @@ func (as *AccountService) CreateAccount(ctx context.Context) (*models.AccountRes
if err != nil {
return nil, err
}
_, err = doCustodialRequest(ctx, req, &r)
_, err = doCustodialRequest(ctx, req, &r)
if err != nil {
return nil, err
}
@@ -118,7 +115,7 @@ func (as *AccountService) FetchVouchers(ctx context.Context, publicKey string) (
return nil, err
}
_, err = doDataRequest(ctx, req, r)
_, err = doDataRequest(ctx, req, r)
if err != nil {
return nil, err
}
@@ -126,7 +123,6 @@ func (as *AccountService) FetchVouchers(ctx context.Context, publicKey string) (
return r, nil
}
// FetchTransactions retrieves the last 10 transactions for a given public key from the data indexer API endpoint
// Parameters:
// - publicKey: The public key associated with the account.
@@ -143,7 +139,7 @@ func (as *AccountService) FetchTransactions(ctx context.Context, publicKey strin
return nil, err
}
_, err = doDataRequest(ctx, req, r)
_, err = doDataRequest(ctx, req, r)
if err != nil {
return nil, err
}
@@ -151,7 +147,6 @@ func (as *AccountService) FetchTransactions(ctx context.Context, publicKey strin
return r, nil
}
// VoucherData retrieves voucher metadata from the data indexer API endpoint.
// Parameters:
// - address: The voucher address.
@@ -173,17 +168,18 @@ func (as *AccountService) VoucherData(ctx context.Context, address string) (*mod
}
func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) {
var okResponse api.OKResponse
var okResponse api.OKResponse
var errResponse api.ErrResponse
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Printf("Failed to make %s request to endpoint: %s with reason: %s", req.Method, req.URL, err.Error())
errResponse.Description = err.Error()
return nil, err
}
defer resp.Body.Close()
log.Printf("Received response for %s: Status Code: %d | Content-Type: %s", req.URL, resp.StatusCode, resp.Header.Get("Content-Type"))
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
@@ -213,11 +209,30 @@ func doRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKRespons
}
func doCustodialRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) {
req.Header.Set("X-GE-KEY", config.CustodialAPIKey)
req.Header.Set("Authorization", "Bearer "+config.CustodialBearerToken)
logRequestDetails(req)
return doRequest(ctx, req, rcpt)
}
func doDataRequest(ctx context.Context, req *http.Request, rcpt any) (*api.OKResponse, error) {
req.Header.Set("X-GE-KEY", config.DataAPIKey)
req.Header.Set("Authorization", "Bearer "+config.DataBearerToken)
logRequestDetails(req)
return doRequest(ctx, req, rcpt)
}
func logRequestDetails(req *http.Request) {
var bodyBytes []byte
contentType := req.Header.Get("Content-Type")
if req.Body != nil {
bodyBytes, err := io.ReadAll(req.Body)
if err != nil {
log.Printf("Error reading request body: %s", err)
return
}
req.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
} else {
bodyBytes = []byte("-")
}
log.Printf("URL: %s | Content-Type: %s | Method: %s| Request Body: %s", req.URL, contentType, req.Method, string(bodyBytes))
}

View File

@@ -1,6 +1,8 @@
LOAD check_identifier 0
RELOAD check_identifier
MAP check_identifier
MOUT back 0
MOUT quit 9
HALT
INCMP _ 0
INCMP quit 9

View File

@@ -0,0 +1 @@
Anwani:{{.check_identifier}}

View File

@@ -1,5 +1,5 @@
MOUT retry 0
MOUT retry 1
MOUT quit 9
HALT
INCMP _ 0
INCMP _ 1
INCMP quit 9

View File

@@ -0,0 +1 @@
Tafadhali thibitisha PIN mpya ya: {{.retrieve_blocked_number}}

View File

@@ -0,0 +1,2 @@
Current family name: {{.get_current_profile_info}}
Enter family name:

View File

@@ -1,5 +1,7 @@
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_familyname flag_allow_update 1
LOAD get_current_profile_info 0
RELOAD get_current_profile_info
MOUT back 0
HALT
LOAD save_familyname 0

View File

@@ -0,0 +1,2 @@
Jina la familia la sasa: {{.get_current_profile_info}}
Weka jina la familia

View File

@@ -0,0 +1,2 @@
Current name: {{.get_current_profile_info}}
Enter your first names:

View File

@@ -1,5 +1,8 @@
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_firstname flag_allow_update 1
LOAD get_current_profile_info 0
RELOAD get_current_profile_info
MAP get_current_profile_info
MOUT back 0
HALT
LOAD save_firstname 0

View File

@@ -0,0 +1,2 @@
Jina la kwanza la sasa {{.get_current_profile_info}}
Weka majina yako ya kwanza:

View File

@@ -0,0 +1,2 @@
Current location: {{.get_current_profile_info}}
Enter your location:

View File

@@ -1,5 +1,7 @@
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_location flag_allow_update 1
LOAD get_current_profile_info 0
RELOAD get_current_profile_info
MOUT back 0
HALT
LOAD save_location 0

View File

@@ -0,0 +1,2 @@
Eneo la sasa {{.get_current_profile_info}}
Weka eneo:

View File

@@ -0,0 +1,2 @@
Current offerings: {{.get_current_profile_info}}
Enter the services or goods you offer:

View File

@@ -1,5 +1,7 @@
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_offerings flag_allow_update 1
LOAD get_current_profile_info 0
RELOAD get_current_profile_info
LOAD save_offerings 0
MOUT back 0
HALT

View File

@@ -0,0 +1,2 @@
Unachouza kwa sasa: {{.get_current_profile_info}}
Weka unachouza

View File

@@ -2,8 +2,8 @@ LOAD reset_account_authorized 16
RELOAD reset_account_authorized
LOAD reset_allow_update 0
RELOAD reset_allow_update
MOUT edit_name 1
MOUT edit_familyname 2
MOUT edit_first_name 1
MOUT edit_family_name 2
MOUT edit_gender 3
MOUT edit_yob 4
MOUT edit_location 5
@@ -12,10 +12,10 @@ MOUT view 7
MOUT back 0
HALT
INCMP my_account 0
INCMP enter_name 1
INCMP enter_familyname 2
INCMP edit_first_name 1
INCMP edit_family_name 2
INCMP select_gender 3
INCMP enter_yob 4
INCMP enter_location 5
INCMP enter_offerings 6
INCMP edit_yob 4
INCMP edit_location 5
INCMP edit_offerings 6
INCMP view_profile 7

View File

@@ -0,0 +1,2 @@
Current year of birth: {{.get_current_profile_info}}
Enter your year of birth

View File

@@ -1,8 +1,12 @@
CATCH incorrect_pin flag_incorrect_pin 1
CATCH update_yob flag_allow_update 1
LOAD get_current_profile_info 0
RELOAD get_current_profile_info
MAP get_current_profile_info
MOUT back 0
HALT
LOAD verify_yob 0
LOAD verify_yob 6
RELOAD verify_yob
CATCH incorrect_date_format flag_incorrect_date_format 1
LOAD save_yob 0
RELOAD save_yob

View File

@@ -0,0 +1,2 @@
Mwaka wa sasa wa kuzaliwa {{.get_current_profile_info}}
Weka mwaka wa kuzaliwa

View File

@@ -1 +0,0 @@
Enter family name:

View File

@@ -1 +0,0 @@
Weka jina la familia

View File

@@ -1 +0,0 @@
Enter your location:

View File

@@ -1 +0,0 @@
Weka eneo:

View File

@@ -1 +0,0 @@
Enter your first names:

View File

@@ -1 +0,0 @@
Weka majina yako ya kwanza:

View File

@@ -1 +0,0 @@
Enter the services or goods you offer:

View File

@@ -1 +0,0 @@
Weka unachouza

View File

@@ -0,0 +1 @@
Weka nambari ya simu ili kutuma ombi la kubadilisha nambari ya siri:

View File

@@ -0,0 +1 @@
Tafadhali weka PIN mpya ya: {{.retrieve_blocked_number}}

View File

@@ -1 +0,0 @@
Enter your year of birth

View File

@@ -1 +0,0 @@
Weka mwaka wa kuzaliwa

View File

@@ -2,5 +2,5 @@ LOAD reset_incorrect_date_format 8
MOUT retry 1
MOUT quit 9
HALT
INCMP enter_yob 1
INCMP _ 1
INCMP quit 9

View File

@@ -0,0 +1 @@
Huna mapendeleo ya kufanya kitendo hiki

View File

@@ -0,0 +1 @@
PIN uliyoweka hailingani.Jaribu tena.

View File

@@ -0,0 +1 @@
PIN uliyoweka hailingani.Jaribu tena.

View File

@@ -0,0 +1 @@
Ombi la kuweka upya PIN ya {{.retrieve_blocked_number}} limefanikiwa

View File

@@ -0,0 +1 @@
Retry

View File

@@ -0,0 +1 @@
Jaribu tena

View File

@@ -1 +1,2 @@
Current gender: {{.get_current_profile_info}}
Select gender:

View File

@@ -1,5 +1,7 @@
CATCH incorrect_pin flag_incorrect_pin 1
CATCH profile_update_success flag_allow_update 1
LOAD get_current_profile_info 0
RELOAD get_current_profile_info
MOUT male 1
MOUT female 2
MOUT unspecified 3
@@ -9,7 +11,3 @@ INCMP _ 0
INCMP set_male 1
INCMP set_female 2
INCMP set_unspecified 3

View File

@@ -1 +1,2 @@
Jinsia ya sasa {{.get_current_profile_info}}
Chagua jinsia

View File

@@ -0,0 +1 @@
Chagua Sarafu

View File

@@ -0,0 +1 @@
Nambari uliyoingiza haijasajiliwa na Sarafu au sio sahihi.

View File

@@ -0,0 +1 @@
Maelezo ya Sarafu