Partly resolves issue #86 Reviewed-on: #90 Co-authored-by: alfred-mk <alfredmwaik@gmail.com> Co-committed-by: alfred-mk <alfredmwaik@gmail.com>
		
			
				
	
	
		
			274 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			274 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package application
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"path"
 | |
| 
 | |
| 	"gopkg.in/leonelquinteros/gotext.v1"
 | |
| 
 | |
| 	"git.defalsify.org/vise.git/asm"
 | |
| 	"git.defalsify.org/vise.git/cache"
 | |
| 	"git.defalsify.org/vise.git/db"
 | |
| 	"git.defalsify.org/vise.git/lang"
 | |
| 	"git.defalsify.org/vise.git/logging"
 | |
| 	"git.defalsify.org/vise.git/persist"
 | |
| 	"git.defalsify.org/vise.git/resource"
 | |
| 	"git.defalsify.org/vise.git/state"
 | |
| 	"git.grassecon.net/grassrootseconomics/sarafu-api/remote"
 | |
| 	"git.grassecon.net/grassrootseconomics/sarafu-vise/internal/sms"
 | |
| 	"git.grassecon.net/grassrootseconomics/sarafu-vise/profile"
 | |
| 	"git.grassecon.net/grassrootseconomics/sarafu-vise/store"
 | |
| 	storedb "git.grassecon.net/grassrootseconomics/sarafu-vise/store/db"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	logg           = logging.NewVanilla().WithDomain("ussdmenuhandler").WithContextKey("SessionId")
 | |
| 	scriptDir      = path.Join("services", "registration")
 | |
| 	translationDir = path.Join(scriptDir, "locale")
 | |
| )
 | |
| 
 | |
| // TODO: this is only in use in testing, should be moved to test domain and/or replaced by asm.FlagParser
 | |
| // FlagManager handles centralized flag management
 | |
| type FlagManager struct {
 | |
| 	*asm.FlagParser
 | |
| }
 | |
| 
 | |
| // NewFlagManager creates a new FlagManager instance
 | |
| func NewFlagManager(csvPath string) (*FlagManager, error) {
 | |
| 	parser := asm.NewFlagParser()
 | |
| 	_, err := parser.Load(csvPath)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("failed to load flag parser: %v", err)
 | |
| 	}
 | |
| 
 | |
| 	return &FlagManager{
 | |
| 		FlagParser: parser,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func (fm *FlagManager) SetDebug() {
 | |
| 	fm.FlagParser = fm.FlagParser.WithDebug()
 | |
| }
 | |
| 
 | |
| // GetFlag retrieves a flag value by its label
 | |
| func (fm *FlagManager) GetFlag(label string) (uint32, error) {
 | |
| 	return fm.FlagParser.GetFlag(label)
 | |
| }
 | |
| 
 | |
| type MenuHandlers struct {
 | |
| 	pe                   *persist.Persister
 | |
| 	st                   *state.State
 | |
| 	ca                   cache.Memory
 | |
| 	userdataStore        store.DataStore
 | |
| 	flagManager          *FlagManager
 | |
| 	accountService       remote.AccountService
 | |
| 	prefixDb             storedb.PrefixDb
 | |
| 	smsService           sms.SmsService
 | |
| 	logDb                store.LogDb
 | |
| 	profile              *profile.Profile
 | |
| 	ReplaceSeparatorFunc func(string) string
 | |
| }
 | |
| 
 | |
| // NewHandlers creates a new instance of the Handlers struct with the provided dependencies.
 | |
| func NewMenuHandlers(appFlags *FlagManager, userdataStore db.Db, logdb db.Db, accountService remote.AccountService, replaceSeparatorFunc func(string) string) (*MenuHandlers, error) {
 | |
| 	if userdataStore == nil {
 | |
| 		return nil, fmt.Errorf("cannot create handler with nil userdata store")
 | |
| 	}
 | |
| 	userDb := &store.UserDataStore{
 | |
| 		Db: userdataStore,
 | |
| 	}
 | |
| 	smsservice := sms.SmsService{
 | |
| 		Accountservice: accountService,
 | |
| 		Userdatastore:  *userDb,
 | |
| 	}
 | |
| 
 | |
| 	logDb := store.LogDb{
 | |
| 		Db: logdb,
 | |
| 	}
 | |
| 
 | |
| 	// Instantiate the SubPrefixDb with "DATATYPE_USERDATA" prefix
 | |
| 	prefix := storedb.ToBytes(db.DATATYPE_USERDATA)
 | |
| 	prefixDb := storedb.NewSubPrefixDb(userdataStore, prefix)
 | |
| 
 | |
| 	h := &MenuHandlers{
 | |
| 		userdataStore:        userDb,
 | |
| 		flagManager:          appFlags,
 | |
| 		accountService:       accountService,
 | |
| 		smsService:           smsservice,
 | |
| 		prefixDb:             prefixDb,
 | |
| 		logDb:                logDb,
 | |
| 		profile:              &profile.Profile{Max: 6},
 | |
| 		ReplaceSeparatorFunc: replaceSeparatorFunc,
 | |
| 	}
 | |
| 	return h, nil
 | |
| }
 | |
| 
 | |
| // SetPersister sets persister instance to the handlers.
 | |
| func (h *MenuHandlers) SetPersister(pe *persist.Persister) {
 | |
| 	if h.pe != nil {
 | |
| 		panic("persister already set")
 | |
| 	}
 | |
| 	h.pe = pe
 | |
| }
 | |
| 
 | |
| // Init initializes the handler for a new session.
 | |
| func (h *MenuHandlers) Init(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | |
| 	var r resource.Result
 | |
| 	if h.pe == nil {
 | |
| 		logg.WarnCtxf(ctx, "handler init called before it is ready or more than once", "state", h.st, "cache", h.ca)
 | |
| 		return r, nil
 | |
| 	}
 | |
| 	defer func() {
 | |
| 		h.Exit()
 | |
| 	}()
 | |
| 
 | |
| 	h.st = h.pe.GetState()
 | |
| 	h.ca = h.pe.GetMemory()
 | |
| 	sessionId, ok := ctx.Value("SessionId").(string)
 | |
| 	if ok {
 | |
| 		ctx = context.WithValue(ctx, "SessionId", sessionId)
 | |
| 	}
 | |
| 
 | |
| 	if h.st == nil || h.ca == nil {
 | |
| 		logg.ErrorCtxf(ctx, "perister fail in handler", "state", h.st, "cache", h.ca)
 | |
| 		return r, fmt.Errorf("cannot get state and memory for handler")
 | |
| 	}
 | |
| 
 | |
| 	logg.DebugCtxf(ctx, "handler has been initialized", "state", h.st, "cache", h.ca)
 | |
| 
 | |
| 	return r, nil
 | |
| }
 | |
| 
 | |
| func (h *MenuHandlers) Exit() {
 | |
| 	h.pe = nil
 | |
| }
 | |
| 
 | |
| // retrieves language codes from the context that can be used for handling translations.
 | |
| func codeFromCtx(ctx context.Context) string {
 | |
| 	var code string
 | |
| 	if ctx.Value("Language") != nil {
 | |
| 		lang := ctx.Value("Language").(lang.Language)
 | |
| 		code = lang.Code
 | |
| 	}
 | |
| 	return code
 | |
| }
 | |
| 
 | |
| // ResetApiCallFailure resets the api call failure flag
 | |
| func (h *MenuHandlers) ResetApiCallFailure(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | |
| 	var res resource.Result
 | |
| 	flag_api_error, _ := h.flagManager.GetFlag("flag_api_call_error")
 | |
| 	res.FlagReset = append(res.FlagReset, flag_api_error)
 | |
| 	return res, nil
 | |
| }
 | |
| 
 | |
| // ResetUnregisteredNumber clears the unregistered number flag in the system,
 | |
| // indicating that a number's registration status should no longer be marked as unregistered.
 | |
| func (h *MenuHandlers) ResetUnregisteredNumber(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | |
| 	var res resource.Result
 | |
| 	flag_unregistered_number, _ := h.flagManager.GetFlag("flag_unregistered_number")
 | |
| 	res.FlagReset = append(res.FlagReset, flag_unregistered_number)
 | |
| 	return res, nil
 | |
| }
 | |
| 
 | |
| // CheckIdentifier retrieves the Public key from the userdatastore under the key: DATA_PUBLIC_KEY and triggers an sms that
 | |
| // will be sent to the associated session id
 | |
| func (h *MenuHandlers) CheckIdentifier(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | |
| 	var res resource.Result
 | |
| 
 | |
| 	smsservice := h.smsService
 | |
| 
 | |
| 	sessionId, ok := ctx.Value("SessionId").(string)
 | |
| 	if !ok {
 | |
| 		return res, fmt.Errorf("missing session")
 | |
| 	}
 | |
| 	store := h.userdataStore
 | |
| 	publicKey, err := store.ReadEntry(ctx, sessionId, storedb.DATA_PUBLIC_KEY)
 | |
| 	if err != nil {
 | |
| 		logg.ErrorCtxf(ctx, "failed to read publicKey entry with", "key", storedb.DATA_PUBLIC_KEY, "error", err)
 | |
| 		return res, err
 | |
| 	}
 | |
| 	res.Content = string(publicKey)
 | |
| 	//trigger an address sms to be delivered to the associated session id
 | |
| 	err = smsservice.SendAddressSMS(ctx)
 | |
| 	if err != nil {
 | |
| 		logg.DebugCtxf(ctx, "Failed to trigger an address sms", "error", err)
 | |
| 		return res, nil
 | |
| 	}
 | |
| 
 | |
| 	return res, nil
 | |
| }
 | |
| 
 | |
| // Setback sets the flag_back_set flag when the navigation is back.
 | |
| func (h *MenuHandlers) SetBack(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | |
| 	var res resource.Result
 | |
| 	flag_back_set, _ := h.flagManager.GetFlag("flag_back_set")
 | |
| 	//TODO:
 | |
| 	//Add check if the navigation is lateral nav instead of checking the input.
 | |
| 	if string(input) == "0" {
 | |
| 		res.FlagSet = append(res.FlagSet, flag_back_set)
 | |
| 	} else {
 | |
| 		res.FlagReset = append(res.FlagReset, flag_back_set)
 | |
| 	}
 | |
| 	return res, nil
 | |
| }
 | |
| 
 | |
| // Quit displays the Thank you message and exits the menu.
 | |
| func (h *MenuHandlers) Quit(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | |
| 	var res resource.Result
 | |
| 
 | |
| 	flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
 | |
| 
 | |
| 	code := codeFromCtx(ctx)
 | |
| 	l := gotext.NewLocale(translationDir, code)
 | |
| 	l.AddDomain("default")
 | |
| 
 | |
| 	res.Content = l.Get("Thank you for using Sarafu. Goodbye!")
 | |
| 	res.FlagReset = append(res.FlagReset, flag_account_authorized)
 | |
| 	return res, nil
 | |
| }
 | |
| 
 | |
| // QuitWithHelp displays helpline information then exits the menu.
 | |
| func (h *MenuHandlers) QuitWithHelp(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | |
| 	var res resource.Result
 | |
| 
 | |
| 	flag_account_authorized, _ := h.flagManager.GetFlag("flag_account_authorized")
 | |
| 
 | |
| 	code := codeFromCtx(ctx)
 | |
| 	l := gotext.NewLocale(translationDir, code)
 | |
| 	l.AddDomain("default")
 | |
| 
 | |
| 	res.Content = l.Get("For more help, please call: 0757628885")
 | |
| 	res.FlagReset = append(res.FlagReset, flag_account_authorized)
 | |
| 	return res, nil
 | |
| }
 | |
| 
 | |
| // ShowBlockedAccount displays a message after an account has been blocked and how to reach support.
 | |
| func (h *MenuHandlers) ShowBlockedAccount(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | |
| 	var res resource.Result
 | |
| 	code := codeFromCtx(ctx)
 | |
| 	l := gotext.NewLocale(translationDir, code)
 | |
| 	l.AddDomain("default")
 | |
| 	res.Content = l.Get("Your account has been locked. For help on how to unblock your account, contact support at: 0757628885")
 | |
| 	return res, nil
 | |
| }
 | |
| 
 | |
| // ClearTemporaryValue empties the DATA_TEMPORARY_VALUE at the main menu to prevent
 | |
| // previously stored data from being accessed
 | |
| func (h *MenuHandlers) ClearTemporaryValue(ctx context.Context, sym string, input []byte) (resource.Result, error) {
 | |
| 	var res resource.Result
 | |
| 	sessionId, ok := ctx.Value("SessionId").(string)
 | |
| 	if !ok {
 | |
| 		return res, fmt.Errorf("missing session")
 | |
| 	}
 | |
| 	userStore := h.userdataStore
 | |
| 
 | |
| 	// clear the temporary value at the start
 | |
| 	err := userStore.WriteEntry(ctx, sessionId, storedb.DATA_TEMPORARY_VALUE, []byte(""))
 | |
| 	if err != nil {
 | |
| 		logg.ErrorCtxf(ctx, "failed to clear DATA_TEMPORARY_VALUE entry with", "key", storedb.DATA_TEMPORARY_VALUE, "value", "empty", "error", err)
 | |
| 		return res, err
 | |
| 	}
 | |
| 	return res, nil
 | |
| }
 |