wip-account-creation #4

Merged
lash merged 143 commits from wip-account-creation into master 2024-08-30 14:37:58 +02:00
5 changed files with 817 additions and 792 deletions
Showing only changes of commit 1a997e8b26 - Show all commits

View File

@ -1,774 +1,21 @@
package main
import (
"bytes"
"context"
"encoding/json"
"flag"
"fmt"
"os"
"path"
"regexp"
"time"
"git.defalsify.org/vise.git/cache"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/lang"
"git.defalsify.org/vise.git/persist"
"git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/state"
"git.grassecon.net/urdt/ussd/internal/models"
"git.grassecon.net/urdt/ussd/internal/server/handlers"
"git.grassecon.net/urdt/ussd/internal/utils"
"git.grassecon.net/urdt/ussd/internal/handlers/ussd"
)
type fsData struct {
path string
st *state.State
}
func codeFromCtx(ctx context.Context) string {
var code string
engine.Logg.DebugCtxf(ctx, "in msg", "ctx", ctx, "val", code)
if ctx.Value("Language") != nil {
lang := ctx.Value("Language").(lang.Language)
code = lang.Code
}
return code
}
func (fsd *fsData) save_firstname(cxt context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
name := string(input)
accountData["FirstName"] = name
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (fsd *fsData) save_familyname(cxt context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
//Save name
secondname := string(input)
accountData["FamilyName"] = secondname
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (fsd *fsData) save_yob(cxt context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
yob := string(input)
accountData["YOB"] = yob
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (fsd *fsData) save_location(cxt context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
location := string(input)
accountData["Location"] = location
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (fsd *fsData) save_gender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
gender := string(input)
switch gender {
case "1":
gender = "Male"
case "2":
gender = "Female"
case "3":
gender = "Other"
}
accountData["Gender"] = gender
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (fsd *fsData) save_offerings(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
offerings := string(input)
accountData["Offerings"] = offerings
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (fsd *fsData) set_language(ctx context.Context, sym string, input []byte) (resource.Result, error) {
inputStr := string(input)
res := resource.Result{}
switch inputStr {
case "0":
res.FlagSet = []uint32{state.FLAG_LANG}
res.Content = "eng"
case "1":
res.FlagSet = []uint32{state.FLAG_LANG}
res.Content = "swa"
default:
}
res.FlagSet = append(res.FlagSet, models.USERFLAG_LANGUAGE_SET)
return res, nil
}
func (fsd *fsData) create_account(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
f, err := os.OpenFile(fp, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return res, err
}
f.Close()
accountResp, err := handlers.CreateAccount()
if err != nil {
fmt.Println("Failed to create account:", err)
return res, err
}
accountData := map[string]string{
"TrackingId": accountResp.Result.TrackingId,
"PublicKey": accountResp.Result.PublicKey,
"CustodialId": accountResp.Result.CustodialId.String(),
"Status": "PENDING",
}
jsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, jsonData, 0644)
if err != nil {
return res, err
}
res.FlagSet = append(res.FlagSet, models.USERFLAG_ACCOUNT_CREATED)
return res, err
}
func (fsd *fsData) reset_unlock_for_update(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
res.FlagReset = append(res.FlagReset, models.USERFLAG_UNLOCKFORUPDATE)
return res, nil
}
func (fsd *fsData) reset_account_unlocked(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_UNLOCKED)
return res, nil
}
func (fsd *fsData) check_identifier(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
res.Content = accountData["PublicKey"]
return res, nil
}
func (fsd *fsData) unlock(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
pin := string(input)
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 1 {
if pin != accountData["AccountPIN"] {
res.FlagSet = append(res.FlagSet, models.USERFLAG_INCORRECTPIN)
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_UNLOCKED)
return res, nil
}
if fsd.st.MatchFlag(models.USERFLAG_ACCOUNT_UNLOCKED, false) {
res.FlagReset = append(res.FlagReset, models.USERFLAG_INCORRECTPIN)
res.FlagSet = append(res.FlagSet, models.USERFLAG_UNLOCKFORUPDATE)
res.FlagSet = append(res.FlagSet, models.USERFLAG_ACCOUNT_UNLOCKED)
} else {
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_UNLOCKED)
}
}
return res, nil
}
func (fsd *fsData) reset_incorrect_pin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
res.FlagReset = append(res.FlagReset, models.USERFLAG_INCORRECTPIN)
return res, nil
}
func (fsd *fsData) check_account_status(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
//status, err := checkAccountStatus(accountData["TrackingId"])
status, err := handlers.CheckAccountStatus(accountData["TrackingId"])
if err != nil {
fmt.Println("Error checking account status:", err)
return res, nil
}
accountData["Status"] = status
if status == "SUCCESS" {
res.FlagSet = append(res.FlagSet, models.USERFLAG_ACCOUNT_SUCCESS)
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_PENDING)
} else {
res.FlagReset = append(res.FlagSet, models.USERFLAG_ACCOUNT_SUCCESS)
res.FlagSet = append(res.FlagReset, models.USERFLAG_ACCOUNT_PENDING)
}
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
return res, nil
}
func (fsd *fsData) quit(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
switch codeFromCtx(ctx) {
case "swa":
res.Content = "Asante kwa kutumia huduma ya Sarafu. Kwaheri!"
default:
res.Content = "Thank you for using Sarafu. Goodbye!"
}
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_UNLOCKED)
return res, nil
}
func (fsd *fsData) verify_yob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
date := string(input)
dateRegex := regexp.MustCompile(`^\d{2}/\d{2}/\d{4}$`)
isCorrectFormat := dateRegex.MatchString(date)
if !isCorrectFormat {
res.FlagSet = append(res.FlagSet, models.USERFLAG_INCORRECTDATEFORMAT)
} else {
res.FlagReset = append(res.FlagReset, models.USERFLAG_INCORRECTDATEFORMAT)
}
return res, nil
}
func (fsd *fsData) reset_incorrect_yob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
res.FlagReset = append(res.FlagReset, models.USERFLAG_INCORRECTDATEFORMAT)
return res, nil
}
func (fsd *fsData) check_balance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
balance, err := handlers.CheckBalance(accountData["PublicKey"])
if err != nil {
return res, nil
}
res.Content = balance
return res, nil
}
func (fsd *fsData) validate_recipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
recipient := string(input)
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if recipient != "0" {
// mimic invalid number check
if recipient == "000" {
res.FlagSet = append(res.FlagSet, models.USERFLAG_INVALID_RECIPIENT)
res.Content = recipient
return res, nil
}
accountData["Recipient"] = recipient
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (fsd *fsData) transaction_reset(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
// reset the recipient
accountData["Recipient"] = ""
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
res.FlagReset = append(res.FlagReset, models.USERFLAG_INVALID_RECIPIENT, models.USERFLAG_INVALID_RECIPIENT_WITH_INVITE)
return res, nil
}
func (fsd *fsData) reset_transaction_amount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
// reset the amount
accountData["Amount"] = ""
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
res.FlagReset = append(res.FlagReset, models.USERFLAG_INVALID_AMOUNT)
return res, nil
}
func (fsd *fsData) max_amount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
// mimic a max amount
res.Content = "10.00"
return res, nil
}
func (fsd *fsData) validate_amount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
amount := string(input)
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if amount != "0" {
// mimic invalid amount
if amount == "00" {
res.FlagSet = append(res.FlagSet, models.USERFLAG_INVALID_AMOUNT)
res.Content = amount
return res, nil
}
res.Content = amount
accountData["Amount"] = amount
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
return res, nil
}
return res, nil
}
func (fsd *fsData) get_recipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
res.Content = accountData["Recipient"]
return res, nil
}
func (fsd *fsData) get_profile_info(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
name := accountData["FirstName"] + " " + accountData["FamilyName"]
gender := accountData["Gender"]
yob := accountData["YOB"]
location := accountData["Location"]
offerings := accountData["Offerings"]
layout := "02/01/2006"
birthdate, err := time.Parse(layout, yob)
if err != nil {
return res, err
}
currentDate := time.Now()
formattedDate := currentDate.Format(layout)
today, err := time.Parse(layout, formattedDate)
if err != nil {
return res, nil
}
age := utils.CalculateAge(birthdate, today)
formattedData := fmt.Sprintf("Name: %s\nGender: %s\nAge: %d\nLocation: %s\nYou provide: %s\n", name, gender, age, location, offerings)
res.Content = formattedData
return res, nil
}
func (fsd *fsData) get_sender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
res.Content = accountData["PublicKey"]
return res, nil
}
func (fsd *fsData) quit_with_balance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
balance, err := handlers.CheckBalance(accountData["PublicKey"])
if err != nil {
return res, nil
}
res.Content = fmt.Sprintf("Your account balance is: %s", balance)
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_UNLOCKED)
return res, nil
}
func (fsd *fsData) save_pin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
accountPIN := string(input)
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
accountData["AccountPIN"] = accountPIN
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
return res, nil
}
func (fsd *fsData) verify_pin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := fsd.path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if bytes.Equal(input, []byte(accountData["AccountPIN"])) {
res.FlagSet = []uint32{models.USERFLAG_VALIDPIN}
res.FlagReset = []uint32{models.USERFLAG_PINMISMATCH}
} else {
res.FlagSet = []uint32{models.USERFLAG_PINMISMATCH}
}
return res, nil
}
var (
scriptDir = path.Join("services", "registration")
)
@ -824,39 +71,38 @@ func main() {
}
fp := path.Join(dp, sessionId)
fs := &fsData{
path: fp,
st: &st,
}
rfs.AddLocalFunc("select_language", fs.set_language)
rfs.AddLocalFunc("create_account", fs.create_account)
rfs.AddLocalFunc("save_pin", fs.save_pin)
rfs.AddLocalFunc("verify_pin", fs.verify_pin)
rfs.AddLocalFunc("check_identifier", fs.check_identifier)
rfs.AddLocalFunc("check_account_status", fs.check_account_status)
rfs.AddLocalFunc("unlock_account", fs.unlock)
rfs.AddLocalFunc("quit", fs.quit)
rfs.AddLocalFunc("check_balance", fs.check_balance)
rfs.AddLocalFunc("validate_recipient", fs.validate_recipient)
rfs.AddLocalFunc("transaction_reset", fs.transaction_reset)
rfs.AddLocalFunc("max_amount", fs.max_amount)
rfs.AddLocalFunc("validate_amount", fs.validate_amount)
rfs.AddLocalFunc("reset_transaction_amount", fs.reset_transaction_amount)
rfs.AddLocalFunc("get_recipient", fs.get_recipient)
rfs.AddLocalFunc("get_sender", fs.get_sender)
rfs.AddLocalFunc("reset_incorrect", fs.reset_incorrect_pin)
rfs.AddLocalFunc("save_firstname", fs.save_firstname)
rfs.AddLocalFunc("save_familyname", fs.save_familyname)
rfs.AddLocalFunc("save_gender", fs.save_gender)
rfs.AddLocalFunc("save_location", fs.save_location)
rfs.AddLocalFunc("save_yob", fs.save_yob)
rfs.AddLocalFunc("save_offerings", fs.save_offerings)
rfs.AddLocalFunc("quit_with_balance", fs.quit_with_balance)
rfs.AddLocalFunc("reset_unlocked", fs.reset_account_unlocked)
rfs.AddLocalFunc("reset_unlock_for_update", fs.reset_unlock_for_update)
rfs.AddLocalFunc("get_profile_info", fs.get_profile_info)
rfs.AddLocalFunc("verify_yob", fs.verify_yob)
rfs.AddLocalFunc("reset_incorrect_date_format", fs.reset_incorrect_yob)
ussdHandlers := ussd.NewHandlers(fp, &st)
rfs.AddLocalFunc("select_language", ussdHandlers.SetLanguage)
Alfred-mk marked this conversation as resolved Outdated
Outdated
Review

can this (and all its methods) be moved to a separate package please?

can this (and all its methods) be moved to a separate package please?
rfs.AddLocalFunc("create_account", ussdHandlers.CreateAccount)
rfs.AddLocalFunc("save_pin", ussdHandlers.SavePin)
rfs.AddLocalFunc("verify_pin", ussdHandlers.VerifyPin)
rfs.AddLocalFunc("check_identifier", ussdHandlers.CheckIdentifier)
rfs.AddLocalFunc("check_account_status", ussdHandlers.CheckAccountStatus)
rfs.AddLocalFunc("unlock_account", ussdHandlers.Unlock)
rfs.AddLocalFunc("quit", ussdHandlers.Quit)
rfs.AddLocalFunc("check_balance", ussdHandlers.CheckBalance)
rfs.AddLocalFunc("validate_recipient", ussdHandlers.ValidateRecipient)
rfs.AddLocalFunc("transaction_reset", ussdHandlers.TransactionReset)
rfs.AddLocalFunc("max_amount", ussdHandlers.MaxAmount)
rfs.AddLocalFunc("validate_amount", ussdHandlers.ValidateAmount)
rfs.AddLocalFunc("reset_transaction_amount", ussdHandlers.ResetTransactionAmount)
rfs.AddLocalFunc("get_recipient", ussdHandlers.GetRecipient)
rfs.AddLocalFunc("get_sender", ussdHandlers.GetSender)
rfs.AddLocalFunc("reset_incorrect", ussdHandlers.ResetIncorrectPin)
rfs.AddLocalFunc("save_firstname", ussdHandlers.SaveFirstname)
rfs.AddLocalFunc("save_familyname", ussdHandlers.SaveFamilyname)
rfs.AddLocalFunc("save_gender", ussdHandlers.SaveGender)
rfs.AddLocalFunc("save_location", ussdHandlers.SaveLocation)
rfs.AddLocalFunc("save_yob", ussdHandlers.SaveYob)
rfs.AddLocalFunc("save_offerings", ussdHandlers.SaveOfferings)
rfs.AddLocalFunc("quit_with_balance", ussdHandlers.QuitWithBalance)
rfs.AddLocalFunc("reset_unlocked", ussdHandlers.ResetAccountUnlocked)
rfs.AddLocalFunc("reset_unlock_for_update", ussdHandlers.ResetUnlockForUpdate)
rfs.AddLocalFunc("get_profile_info", ussdHandlers.GetProfileInfo)
rfs.AddLocalFunc("verify_yob", ussdHandlers.VerifyYob)
rfs.AddLocalFunc("reset_incorrect_date_format", ussdHandlers.ResetIncorrectYob)
cont, err := en.Init(ctx)
en.SetDebugger(engine.NewSimpleDebug(nil))

View File

@ -1,4 +1,4 @@
package handlers
package server
import (
"encoding/json"
@ -9,7 +9,6 @@ import (
"git.grassecon.net/urdt/ussd/internal/models"
)
func CheckAccountStatus(trackingId string) (string, error) {
resp, err := http.Get(config.TrackStatusURL + trackingId)
if err != nil {

View File

@ -1,4 +1,4 @@
package handlers
package server
import (
"encoding/json"

View File

@ -1,4 +1,4 @@
package handlers
package server
import (
"encoding/json"

View File

@ -0,0 +1,780 @@
package ussd
import (
"bytes"
"context"
"encoding/json"
"fmt"
"os"
"regexp"
"time"
"git.defalsify.org/vise.git/engine"
"git.defalsify.org/vise.git/lang"
"git.defalsify.org/vise.git/resource"
"git.defalsify.org/vise.git/state"
"git.grassecon.net/urdt/ussd/internal/models"
"git.grassecon.net/urdt/ussd/internal/handlers/server"
"git.grassecon.net/urdt/ussd/internal/utils"
)
type FSData struct {
Path string
St *state.State
}
type Handlers struct {
fs *FSData
}
func NewHandlers(path string, st *state.State) *Handlers {
return &Handlers{
fs: &FSData{
Path: path,
St: st,
},
}
}
func (h *Handlers) SetLanguage(ctx context.Context, sym string, input []byte) (resource.Result, error) {
inputStr := string(input)
res := resource.Result{}
switch inputStr {
case "0":
res.FlagSet = []uint32{state.FLAG_LANG}
res.Content = "eng"
case "1":
res.FlagSet = []uint32{state.FLAG_LANG}
res.Content = "swa"
default:
}
res.FlagSet = append(res.FlagSet, models.USERFLAG_LANGUAGE_SET)
return res, nil
}
func (h *Handlers) CreateAccount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
f, err := os.OpenFile(fp, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return res, err
}
f.Close()
accountResp, err := server.CreateAccount()
if err != nil {
fmt.Println("Failed to create account:", err)
return res, err
}
accountData := map[string]string{
"TrackingId": accountResp.Result.TrackingId,
"PublicKey": accountResp.Result.PublicKey,
"CustodialId": accountResp.Result.CustodialId.String(),
"Status": "PENDING",
}
jsonData, err := json.Marshal(accountData)
Alfred-mk marked this conversation as resolved Outdated
Outdated
Review

should not be necessary to set the flag when it already has been set and persisted. That will reduce one file access call each run.

should not be necessary to set the flag when it already has been set and persisted. That will reduce one file access call each run.
if err != nil {
return res, err
}
err = os.WriteFile(fp, jsonData, 0644)
if err != nil {
return res, err
}
res.FlagSet = append(res.FlagSet, models.USERFLAG_ACCOUNT_CREATED)
return res, err
}
func (h *Handlers) SavePin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
accountPIN := string(input)
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
accountData["AccountPIN"] = accountPIN
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
return res, nil
}
func (h *Handlers) VerifyPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if bytes.Equal(input, []byte(accountData["AccountPIN"])) {
res.FlagSet = []uint32{models.USERFLAG_VALIDPIN}
res.FlagReset = []uint32{models.USERFLAG_PINMISMATCH}
} else {
res.FlagSet = []uint32{models.USERFLAG_PINMISMATCH}
}
return res, nil
}
func codeFromCtx(ctx context.Context) string {
var code string
engine.Logg.DebugCtxf(ctx, "in msg", "ctx", ctx, "val", code)
if ctx.Value("Language") != nil {
lang := ctx.Value("Language").(lang.Language)
code = lang.Code
}
return code
}
func (h *Handlers) SaveFirstname(cxt context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
name := string(input)
accountData["FirstName"] = name
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
Alfred-mk marked this conversation as resolved Outdated
Outdated
Review

I prefer putting regexes like this on top of the file as a const.

I prefer putting regexes like this on top of the file as a const.
return res, nil
}
func (h *Handlers) SaveFamilyname(cxt context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
//Save name
secondname := string(input)
accountData["FamilyName"] = secondname
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (h *Handlers) SaveYob(cxt context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
yob := string(input)
accountData["YOB"] = yob
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (h *Handlers) SaveLocation(cxt context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
location := string(input)
accountData["Location"] = location
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (h *Handlers) SaveGender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
gender := string(input)
switch gender {
case "1":
gender = "Male"
case "2":
gender = "Female"
case "3":
gender = "Other"
}
accountData["Gender"] = gender
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
Outdated
Review

Please change to "Unspecified"

Please change to "Unspecified"
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (h *Handlers) SaveOfferings(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 0 {
offerings := string(input)
accountData["Offerings"] = offerings
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (h *Handlers) ResetUnlockForUpdate(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
Outdated
Review

"unlocked" risks being an ambiguous term when we introduce account blocking due to direct action or incorrect pin attempts. can we rename it please?

"unlocked" risks being an ambiguous term when we introduce account blocking due to direct action or incorrect pin attempts. can we rename it please?
res.FlagReset = append(res.FlagReset, models.USERFLAG_UNLOCKFORUPDATE)
return res, nil
}
func (h *Handlers) ResetAccountUnlocked(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_UNLOCKED)
return res, nil
}
func (h *Handlers) CheckIdentifier(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
res.Content = accountData["PublicKey"]
return res, nil
}
func (h *Handlers) Unlock(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
pin := string(input)
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if len(input) > 1 {
if pin != accountData["AccountPIN"] {
res.FlagSet = append(res.FlagSet, models.USERFLAG_INCORRECTPIN)
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_UNLOCKED)
return res, nil
}
if h.fs.St.MatchFlag(models.USERFLAG_ACCOUNT_UNLOCKED, false) {
res.FlagReset = append(res.FlagReset, models.USERFLAG_INCORRECTPIN)
res.FlagSet = append(res.FlagSet, models.USERFLAG_UNLOCKFORUPDATE)
res.FlagSet = append(res.FlagSet, models.USERFLAG_ACCOUNT_UNLOCKED)
} else {
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_UNLOCKED)
}
}
return res, nil
}
func (h *Handlers) ResetIncorrectPin(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
res.FlagReset = append(res.FlagReset, models.USERFLAG_INCORRECTPIN)
return res, nil
}
func (h *Handlers) CheckAccountStatus(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
//status, err := checkAccountStatus(accountData["TrackingId"])
status, err := server.CheckAccountStatus(accountData["TrackingId"])
if err != nil {
fmt.Println("Error checking account status:", err)
return res, nil
}
accountData["Status"] = status
if status == "SUCCESS" {
res.FlagSet = append(res.FlagSet, models.USERFLAG_ACCOUNT_SUCCESS)
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_PENDING)
Alfred-mk marked this conversation as resolved Outdated
Outdated
Review

were we using gettext here too?

were we using gettext here too?
} else {
res.FlagReset = append(res.FlagSet, models.USERFLAG_ACCOUNT_SUCCESS)
res.FlagSet = append(res.FlagReset, models.USERFLAG_ACCOUNT_PENDING)
}
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
return res, nil
}
func (h *Handlers) Quit(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
switch codeFromCtx(ctx) {
case "swa":
res.Content = "Asante kwa kutumia huduma ya Sarafu. Kwaheri!"
default:
res.Content = "Thank you for using Sarafu. Goodbye!"
}
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_UNLOCKED)
return res, nil
}
func (h *Handlers) VerifyYob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
date := string(input)
dateRegex := regexp.MustCompile(`^\d{2}/\d{2}/\d{4}$`)
isCorrectFormat := dateRegex.MatchString(date)
if !isCorrectFormat {
res.FlagSet = append(res.FlagSet, models.USERFLAG_INCORRECTDATEFORMAT)
} else {
res.FlagReset = append(res.FlagReset, models.USERFLAG_INCORRECTDATEFORMAT)
}
return res, nil
}
func (h *Handlers) ResetIncorrectYob(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
res.FlagReset = append(res.FlagReset, models.USERFLAG_INCORRECTDATEFORMAT)
return res, nil
}
func (h *Handlers) CheckBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
balance, err := server.CheckBalance(accountData["PublicKey"])
if err != nil {
return res, nil
}
res.Content = balance
return res, nil
}
func (h *Handlers) ValidateRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
recipient := string(input)
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if recipient != "0" {
// mimic invalid number check
if recipient == "000" {
res.FlagSet = append(res.FlagSet, models.USERFLAG_INVALID_RECIPIENT)
res.Content = recipient
return res, nil
}
accountData["Recipient"] = recipient
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
}
return res, nil
}
func (h *Handlers) TransactionReset(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
// reset the recipient
accountData["Recipient"] = ""
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
res.FlagReset = append(res.FlagReset, models.USERFLAG_INVALID_RECIPIENT, models.USERFLAG_INVALID_RECIPIENT_WITH_INVITE)
return res, nil
}
func (h *Handlers) ResetTransactionAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
// reset the amount
accountData["Amount"] = ""
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
res.FlagReset = append(res.FlagReset, models.USERFLAG_INVALID_AMOUNT)
return res, nil
}
func (h *Handlers) MaxAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
// mimic a max amount
res.Content = "10.00"
return res, nil
}
func (h *Handlers) ValidateAmount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
amount := string(input)
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
if amount != "0" {
// mimic invalid amount
if amount == "00" {
res.FlagSet = append(res.FlagSet, models.USERFLAG_INVALID_AMOUNT)
res.Content = amount
return res, nil
}
res.Content = amount
accountData["Amount"] = amount
updatedJsonData, err := json.Marshal(accountData)
if err != nil {
return res, err
}
err = os.WriteFile(fp, updatedJsonData, 0644)
if err != nil {
return res, err
}
return res, nil
}
return res, nil
}
func (h *Handlers) GetRecipient(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
res.Content = accountData["Recipient"]
return res, nil
}
func (h *Handlers) GetProfileInfo(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
name := accountData["FirstName"] + " " + accountData["FamilyName"]
gender := accountData["Gender"]
yob := accountData["YOB"]
location := accountData["Location"]
offerings := accountData["Offerings"]
layout := "02/01/2006"
birthdate, err := time.Parse(layout, yob)
if err != nil {
return res, err
}
currentDate := time.Now()
formattedDate := currentDate.Format(layout)
today, err := time.Parse(layout, formattedDate)
if err != nil {
return res, nil
}
age := utils.CalculateAge(birthdate, today)
formattedData := fmt.Sprintf("Name: %s\nGender: %s\nAge: %d\nLocation: %s\nYou provide: %s\n", name, gender, age, location, offerings)
res.Content = formattedData
return res, nil
}
func (h *Handlers) GetSender(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
res.Content = accountData["PublicKey"]
return res, nil
}
func (h *Handlers) QuitWithBalance(ctx context.Context, sym string, input []byte) (resource.Result, error) {
res := resource.Result{}
fp := h.fs.Path + "_data"
jsonData, err := os.ReadFile(fp)
if err != nil {
return res, err
}
var accountData map[string]string
err = json.Unmarshal(jsonData, &accountData)
if err != nil {
return res, err
}
balance, err := server.CheckBalance(accountData["PublicKey"])
if err != nil {
return res, nil
}
res.Content = fmt.Sprintf("Your account balance is: %s", balance)
res.FlagReset = append(res.FlagReset, models.USERFLAG_ACCOUNT_UNLOCKED)
return res, nil
}