Skip to content

Commit

Permalink
Add signup for all telegram user.
Browse files Browse the repository at this point in the history
  • Loading branch information
masudur-rahman committed Mar 2, 2024
1 parent 92d5f5e commit aedb6b2
Show file tree
Hide file tree
Showing 41 changed files with 648 additions and 311 deletions.
27 changes: 20 additions & 7 deletions api/handlers/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,11 @@ func sendAccountTypeQuery(ctx telebot.Context, callbackOpts CallbackOptions) err
}

func sendAccountInfoQuery(ctx telebot.Context, callbackOpts CallbackOptions) error {
msg, err := ctx.Bot().Reply(ctx.Message(), `Reply to this Message with the following data
msg, err := ctx.Bot().Reply(ctx.Message(), fmt.Sprintf(`Reply to this Message with the following data
<id> <account name>
i.e.: brac "BRAC Bank"
`, &telebot.SendOptions{
<short name> <account name>
i.e.: %v
`, accountExample(callbackOpts.Account.Type)), &telebot.SendOptions{
ReplyTo: ctx.Message(),
})
if err != nil {
Expand All @@ -63,11 +63,24 @@ i.e.: brac "BRAC Bank"
return nil
}

func accountExample(typ models.AccountType) string {
if typ == models.CashAccount {
return "cash \"Cash in Hand\""
}
return "brac \"BRAC Bank\""
}

func processAccountCreation(ctx telebot.Context, aop AccountCallbackOptions) error {
user, err := all.GetServices().User.GetUserByTelegramID(ctx.Sender().ID)
if err != nil {
return ctx.Send(models.ErrCommonResponse(err))
}

acc := &models.Account{
ID: aop.ID,
Type: aop.Type,
Name: aop.Name,
ID: aop.ID,
UserID: user.ID,
Type: aop.Type,
Name: aop.Name,
}
if err := all.GetServices().Account.CreateAccount(acc); err != nil {
log.Println(err)
Expand Down
73 changes: 62 additions & 11 deletions api/handlers/bot.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"time"

"github.com/masudur-rahman/expense-tracker-bot/configs"
"github.com/masudur-rahman/expense-tracker-bot/infra/logr"
"github.com/masudur-rahman/expense-tracker-bot/models"
"github.com/masudur-rahman/expense-tracker-bot/modules/google"
"github.com/masudur-rahman/expense-tracker-bot/pkg"
Expand All @@ -19,6 +20,31 @@ import (
"gopkg.in/telebot.v3"
)

func StartTrackingExpenses(ctx telebot.Context) error {
us := all.GetServices().User
user, err := us.GetUserByTelegramID(ctx.Sender().ID)
if err == nil {
return ctx.Send(fmt.Sprintf("Welcome back %v %v !",
user.FirstName, user.LastName))
}
if models.IsErrNotFound(err) {
user = &models.User{
TelegramID: ctx.Sender().ID,
Username: ctx.Sender().Username,
FirstName: ctx.Sender().FirstName,
LastName: ctx.Sender().LastName,
}
if err = us.SignUp(user); err == nil {
return ctx.Send(fmt.Sprintf(`Hello %v %v!
Welcome to Expense Tracker !
`, ctx.Sender().FirstName, ctx.Sender().LastName))
}
}

logr.DefaultLogger.Errorw("Start error", "error", err.Error())
return ctx.Send("Some error occurred")
}

func Welcome(ctx telebot.Context) error {
return ctx.Send(fmt.Sprintf(`Hello %v %v!
Welcome to Expense Tracker !
Expand All @@ -45,7 +71,12 @@ func New(ctx telebot.Context) error {
}

func ListUsers(ctx telebot.Context) error {
users, err := all.GetServices().User.ListUsers()
user, err := all.GetServices().User.GetUserByTelegramID(ctx.Sender().ID)
if err != nil {
return ctx.Send(models.ErrCommonResponse(err))
}

users, err := all.GetServices().DebtorCreditor.ListDebtorCreditors(user.ID)
if err != nil {
return ctx.Send(err.Error())
}
Expand All @@ -62,9 +93,14 @@ Syntax unknown.
Format /newuser <id> <name> <email>
`)
}
if err := all.GetServices().User.CreateUser(&models.User{
ID: ui[1],
Name: ui[2],
user, err := all.GetServices().User.GetUserByTelegramID(ctx.Sender().ID)
if err != nil {
return ctx.Send(err.Error())
}
if err := all.GetServices().DebtorCreditor.CreateDebtorCreditor(&models.DebtorsCreditors{
UserID: user.ID,
NickName: ui[1],
FullName: ui[2],
Email: func() string {
if len(ui) >= 4 {
return ui[3]
Expand All @@ -76,7 +112,7 @@ Format /newuser <id> <name> <email>
return ctx.Send(err.Error())
}

return ctx.Send("New User added!")
return ctx.Send("New DebtorsCreditors added!")
}

func AddAccount(ctx telebot.Context) error {
Expand All @@ -102,7 +138,12 @@ Format /new <type> <unique-name> <Account Name>
}

func ListAccounts(ctx telebot.Context) error {
accounts, err := all.GetServices().Account.ListAccounts()
user, err := all.GetServices().User.GetUserByTelegramID(ctx.Sender().ID)
if err != nil {
return ctx.Send(models.ErrCommonResponse(err))
}

accounts, err := all.GetServices().Account.ListAccounts(user.ID)
if err != nil {
return err
}
Expand All @@ -128,7 +169,7 @@ If users will be able to select options from the UI, it's ideal to design the in
2. Subcategory: Based on the selected type, present the relevant subcategories as options for the user to choose from. Display them as buttons or in a dropdown menu.
3. Amount: Once the subcategory is selected, prompt the user to enter the monetary amount of the transaction.
4. SrcID/DstID: Depending on the type of transaction, provide the appropriate options for the source ID (for Expense/Transfer) or destination ID (for Income/Transfer). This could be a dropdown menu or a list of selectable options.
5. User (for Loan/Borrow): If the selected subcategory involves a person (Loan or Borrow), present the relevant users as options for the user to select from. Display them as buttons or in a dropdown menu.
5. DebtorsCreditors (for Loan/Borrow): If the selected subcategory involves a person (Loan or Borrow), present the relevant users as options for the user to select from. Display them as buttons or in a dropdown menu.
6. Remarks: Provide an optional input field for the user to enter any additional remarks or notes related to the transaction.
By structuring the input sequence in this way, users can easily navigate through the available options and make their selections. It enhances the user experience by presenting a guided interface that reduces the chance of errors or confusion during the input process.
Expand All @@ -153,7 +194,7 @@ func parseTransactionFlags(txnString string) (TransactionCallbackOptions, error)
set.StringVarP(&txnOpts.SubcategoryID, "subcat", "s", "misc-misc", "Subcategory for the transaction")
set.StringVarP(&txnOpts.SrcID, "src", "f", "cash", "Source account for the transaction")
set.StringVarP(&txnOpts.DstID, "dst", "d", "", "Destination account for the transaction")
set.StringVarP(&txnOpts.UserID, "user", "u", "", "User associated with the loan/borrow")
set.StringVarP(&txnOpts.DebtorCreditorName, "user", "u", "", "DebtorsCreditors associated with the loan/borrow")
set.StringVarP(&txnOpts.Remarks, "remarks", "r", "", "Remarks for the transaction")
txnOpts.Type = models.TransactionType(typ)

Expand Down Expand Up @@ -192,7 +233,7 @@ func parseTransactionFlags(txnString string) (TransactionCallbackOptions, error)
// Type: models.TransactionType(txnOpts.Type),
// SrcID: txnOpts.SrcID,
// DstID: txnOpts.DstID,
// UserID: txnOpts.UserID,
// DebtorCreditorName: txnOpts.DebtorCreditorName,
// Timestamp: time.Now().Unix(),
// Remarks: txnOpts.Remarks,
// }
Expand All @@ -205,7 +246,12 @@ func parseTransactionFlags(txnString string) (TransactionCallbackOptions, error)
//}

func ListTransactions(ctx telebot.Context) error {
txns, err := all.GetServices().Txn.ListTransactions()
user, err := all.GetServices().User.GetUserByTelegramID(ctx.Sender().ID)
if err != nil {
return ctx.Send(models.ErrCommonResponse(err))
}

txns, err := all.GetServices().Txn.ListTransactions(user.ID)
if err != nil {
return err
}
Expand All @@ -221,7 +267,12 @@ func ListTransactions(ctx telebot.Context) error {
}

func ListExpenses(ctx telebot.Context) error {
txns, err := all.GetServices().Txn.ListTransactionsByTime(models.ExpenseTransaction, pkg.StartOfMonth().Unix(), time.Now().Unix())
user, err := all.GetServices().User.GetUserByTelegramID(ctx.Sender().ID)
if err != nil {
return ctx.Send(models.ErrCommonResponse(err))
}

txns, err := all.GetServices().Txn.ListTransactionsByTime(user.ID, models.ExpenseTransaction, pkg.StartOfMonth().Unix(), time.Now().Unix())
if err != nil {
return err
}
Expand Down
63 changes: 40 additions & 23 deletions api/handlers/callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const (
SummaryTypeCallback CallbackType = "Summary"
ReportTypeCallback CallbackType = "Report"
AccountTypeCallback CallbackType = "Account"
UserTypeCallback CallbackType = "User"
UserTypeCallback CallbackType = "DebtorsCreditors"

StepTxnType NextStep = "txn-type"
StepAmount NextStep = "txn-amount"
Expand All @@ -42,27 +42,28 @@ const (
)

type CallbackOptions struct {
Type CallbackType `json:"type"`
Transaction TransactionCallbackOptions `json:"transaction,omitempty"`
Summary SummaryCallbackOptions `json:"summary,omitempty"`
Report ReportCallbackOptions `json:"report,omitempty"`
Account AccountCallbackOptions `json:"account,omitempty"`
User UserCallbackOptions `json:"user,omitempty"`
Category TxnCategoryCallbackOptions `json:"category,omitempty"`
Type CallbackType `json:"type"`
Transaction TransactionCallbackOptions `json:"transaction,omitempty"`
Summary SummaryCallbackOptions `json:"summary,omitempty"`
Report ReportCallbackOptions `json:"report,omitempty"`
Account AccountCallbackOptions `json:"account,omitempty"`
User UserCallbackOptions `json:"user,omitempty"`
Category TxnCategoryCallbackOptions `json:"category,omitempty"`
LastSelectedValue string
}

type TransactionCallbackOptions struct {
NextStep NextStep `json:"nextStep"`

Type models.TransactionType `json:"type"`

Amount float64 `json:"amount,omitempty"`
SrcID string `json:"srcID,omitempty"`
DstID string `json:"dstID,omitempty"`
CategoryID string `json:"catID,omitempty"`
SubcategoryID string `json:"subcatID,omitempty"`
UserID string `json:"userID,omitempty"`
Remarks string `json:"remarks,omitempty"`
Amount float64 `json:"amount,omitempty"`
SrcID string `json:"srcID,omitempty"`
DstID string `json:"dstID,omitempty"`
CategoryID string `json:"catID,omitempty"`
SubcategoryID string `json:"subcatID,omitempty"`
DebtorCreditorName string `json:"userID,omitempty"`
Remarks string `json:"remarks,omitempty"`
}

var callbackData = make(map[int]CallbackOptions) // map[messageID]CallbackOptions
Expand Down Expand Up @@ -120,37 +121,44 @@ func Callback(ctx telebot.Context) error {
}

func handleTransactionCallback(ctx telebot.Context, callbackOpts CallbackOptions) error {
// Type -> Amount -> SrcID (and/or) DstID -> Category -> Subcategory -> (UserID) -> Remarks
// Type -> Amount -> SrcID (and/or) DstID -> Category -> Subcategory -> (DebtorCreditorName) -> Remarks
txn := callbackOpts.Transaction
switch txn.NextStep {
case StepTxnType:
callbackOpts.LastSelectedValue = fmt.Sprintf("Selected Type: *%v*\n\n", callbackOpts.Transaction.Type)
return sendTransactionAmountTypeQuery(ctx, callbackOpts)
case StepAmount:
callbackOpts.LastSelectedValue = fmt.Sprintf("Selected Amount: *%v*\n\n", callbackOpts.Transaction.Amount)
if txn.Type == models.IncomeTransaction {
return sendTransactionDstTypeQuery(ctx, callbackOpts)
} else {
return sendTransactionSrcTypeQuery(ctx, callbackOpts)
}
case StepSrcID:
callbackOpts.LastSelectedValue = fmt.Sprintf("Selected Source: *%v*\n\n", callbackOpts.Transaction.SrcID)
if txn.Type == models.TransferTransaction {
return sendTransactionDstTypeQuery(ctx, callbackOpts)
} else {
return sendTransactionCategoryQuery(ctx, callbackOpts)
}
case StepDstID:
callbackOpts.LastSelectedValue = fmt.Sprintf("Selected Destination: *%v*\n\n", callbackOpts.Transaction.DstID)
return sendTransactionCategoryQuery(ctx, callbackOpts)
case StepCategory:
callbackOpts.LastSelectedValue = fmt.Sprintf("Selected Category: *%v*\n\n", callbackOpts.Transaction.CategoryID)
return sendTransactionSubcategoryQuery(ctx, callbackOpts)
case StepSubcategory:
callbackOpts.LastSelectedValue = fmt.Sprintf("Selected Subcategory: *%v*\n\n", callbackOpts.Transaction.SubcategoryID)
if loanOrBorrowTypeTransaction(callbackOpts) {
return sendTransactionUserQuery(ctx, callbackOpts)
} else {
return sendTransactionRemarksQuery(ctx, callbackOpts)
}
case StepUser:
callbackOpts.LastSelectedValue = fmt.Sprintf("Selected User: *%v*\n\n", callbackOpts.Transaction.DebtorCreditorName)
return sendTransactionRemarksQuery(ctx, callbackOpts)
case StepRemarks:
err := processTransaction(callbackOpts.Transaction)
err := processTransaction(ctx, callbackOpts.Transaction)
if err != nil {
return ctx.Send(err.Error())
}
Expand All @@ -160,16 +168,19 @@ func handleTransactionCallback(ctx telebot.Context, callbackOpts CallbackOptions
}
}

func setLastSelectedValue(callbackOpts *CallbackOptions) {

}

func parseCallbackOptions(ctx telebot.Context) (CallbackOptions, error) {
var callbackOpts CallbackOptions
err := cache.FetchData(ctx.Callback().Data, &callbackOpts)
return callbackOpts, err
}

func TransactionTextCallback(ctx telebot.Context) error {
fmt.Println(ctx.Text(), "<==>", ctx.Message().Text)
if ctx.Update().Message.ReplyTo == nil {
if err := handleTransactionFromRegularText(ctx.Text()); err != nil {
if err := handleTransactionFromRegularText(ctx); err != nil {
return ctx.Send(err.Error())
}
return ctx.Send("Transaction added successfully!")
Expand Down Expand Up @@ -240,8 +251,8 @@ func handleUserTypeTextCallback(ctx telebot.Context, callbackOpts CallbackOption
return ctx.Reply("must contain <id> <name> <email>")
}
callbackOpts.User = UserCallbackOptions{
ID: info[0],
Name: info[1],
NickName: info[0],
FullName: info[1],
Email: func() string {
if len(info) > 2 {
return info[2]
Expand All @@ -252,10 +263,16 @@ func handleUserTypeTextCallback(ctx telebot.Context, callbackOpts CallbackOption
return processUserCreation(ctx, callbackOpts.User)
}

func handleTransactionFromRegularText(text string) error {
txn, err := transaction.ParseTransaction(text)
func handleTransactionFromRegularText(ctx telebot.Context) error {
user, err := all.GetServices().User.GetUserByTelegramID(ctx.Sender().ID)
if err != nil {
return err
}

txn, err := transaction.ParseTransaction(ctx.Text())
if err != nil {
return err
}
txn.UserID = user.ID
return all.GetServices().Txn.AddTransaction(txn)
}
17 changes: 12 additions & 5 deletions api/handlers/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package handlers

import (
"bytes"
"fmt"
"strings"
"text/template"
"time"

"github.com/masudur-rahman/expense-tracker-bot/models"
"github.com/masudur-rahman/expense-tracker-bot/models/gqtypes"
"github.com/masudur-rahman/expense-tracker-bot/modules/convert"
"github.com/masudur-rahman/expense-tracker-bot/pkg"
Expand Down Expand Up @@ -48,9 +50,9 @@ func generateReportDurationInlineButton(callbackOpts CallbackOptions) []telebot.
}

func handleReportCallback(ctx telebot.Context, callbackOpts CallbackOptions) error {
report, err := generateReport(callbackOpts.Report)
report, err := generateReport(ctx, callbackOpts.Report)
if err != nil {
return ctx.Send(err.Error())
return ctx.Send(models.ErrCommonResponse(err))
}

if err = generateTransactionReportFromTemplate(report); err != nil {
Expand All @@ -63,16 +65,21 @@ func handleReportCallback(ctx telebot.Context, callbackOpts CallbackOptions) err
})
}

func generateReport(rop ReportCallbackOptions) (gqtypes.Report, error) {
func generateReport(ctx telebot.Context, rop ReportCallbackOptions) (gqtypes.Report, error) {
now, startTime := time.Now(), calculateStartTime(rop.Duration)

svc := all.GetServices()
txns, err := svc.Txn.ListTransactionsByTime("", startTime.Unix(), now.Unix())
user, err := svc.User.GetUserByTelegramID(ctx.Sender().ID)
if err != nil {
return gqtypes.Report{}, err
}

txns, err := svc.Txn.ListTransactionsByTime(user.ID, "", startTime.Unix(), now.Unix())
if err != nil {
return gqtypes.Report{}, err
}

report := gqtypes.Report{}
report := gqtypes.Report{Name: fmt.Sprintf("%v %v", user.FirstName, user.LastName)}
txnApis := make([]gqtypes.Transaction, 0, len(txns))
for _, txn := range txns {
txnApis = append(txnApis, convert.ToTransactionAPIFormat(txn))
Expand Down
Loading

0 comments on commit aedb6b2

Please sign in to comment.