From 305c20e9be729b7d8a9e6a6c79005077fd9e10f1 Mon Sep 17 00:00:00 2001 From: Alexander Trakhimenok <533159+trakhimenok@users.noreply.github.com> Date: Tue, 19 Dec 2023 19:34:19 +0000 Subject: [PATCH] feat: CreateBotUserRecord --- README-DB.md | 37 ++++++++++++++++ README.md | 65 +++++++++++++++++++---------- botsfw/botsdal/dal_bot.go | 3 ++ botsfw/botsdal/dal_bot_user.go | 31 ++++++++++++-- botsfw/botsdal/dal_bot_user_test.go | 58 +++++++++++++++++++++---- botsfw/botsdal/dal_platform.go | 3 ++ botsfw/webhook_context.go | 4 +- botsfw/webhook_context_base.go | 41 ++++++++++-------- 8 files changed, 189 insertions(+), 53 deletions(-) create mode 100644 README-DB.md diff --git a/README-DB.md b/README-DB.md new file mode 100644 index 0000000..9b05a09 --- /dev/null +++ b/README-DB.md @@ -0,0 +1,37 @@ +# Bots Framework Database + +The bot framework uses [dalgo](https://github.com/dal-go) (_Db Abstraction Layer in GO_) to work with different +databases in a unified way. + +It was designed to be able to work with nested collections (e.g. with Firestore) +but thanks to `dalgo` it will be able to work with any database supported by `dalgo`. +Including relational SQL databases like MySQL, PostgreSQL, etc. + +## Database structure + +- `botPlafforms` collection + - `bots` collection + - `botUsers` collection + - `botChats` collection + +In case if you use relational SQL databases, collections will be tables and documents will be rows. +The parent keys will be foreign key fields. You would not need `botPlafforms` & `bots` tables in this case. + +- `botUsers` table + - Platform string field + - Bot string field +- `botChats` table + - Platform string field + - Bot string field + +### botPlatforms collection + +Contains the bot platforms like `telegram`, `whatsapp`, etc. + +#### bots collection + +Contains the bots. Each bot has a unique id. + +##### botUsers collection + +Contains the bot users. Each bot user has a unique id. \ No newline at end of file diff --git a/README.md b/README.md index 646356a..9ce95d0 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,26 @@ # πŸ‹οΈ Strongo [Bots Go Framework](https://github.com/bots-go-framework) + A [Go language](https://golang.org/) framework to develop bots for messengers. -Developed & shared by [Sneat-co](https://github.com/sneat-co) & [Strongo](https://github.com/strongo) teams -with usage of [dalgo](https://github.com/dal-go) library. +Developed & shared by [Sneat-co](https://github.com/sneat-co) & [Strongo](https://github.com/strongo) teams +with usage of [dalgo](https://github.com/dal-go) library. **Reasons to use**: - - * Same code can work across different messenger (_Telegram, Facebook Messenger, Viber, Skype, Line, Kik, WeChat, etc._) - * You can tune your code to a specific messenger's UX. - * i18n & l10n support (_multilingual_) - * Can be hosted in cloud or just as a standard Go HTTP server. Supports [AppEngine](https://cloud.google.com/appengine/) standard environment. - * It's fast +* Same code can work across different messenger (_Telegram, Facebook Messenger, Viber, Skype, Line, Kik, WeChat, etc._) +* You can tune your code to a specific messenger's UX. +* i18n & l10n support (_multilingual_) +* Can be hosted in cloud or just as a standard Go HTTP server. Supports [AppEngine](https://cloud.google.com/appengine/) + standard environment. +* It's fast ## β™Ί Continuous Integration + [![Build and Test](https://github.com/strongo/bots-framework/actions/workflows/go.yml/badge.svg)](https://github.com/bots-go-framework/bots-fw/actions/workflows/go.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/strongo/bots-framework?cache=1)](https://goreportcard.com/report/github.com/strongo/bots-framework) [![GoDoc](https://godoc.org/github.com/strongo/bots-framework?status.svg)](https://godoc.org/github.com/strongo/bots-framework) -[![Coverage Status](https://coveralls.io/repos/github/bots-go-framework/bots-fw/badge.svg?branch=main)](https://coveralls.io/github/bots-go-framework/bots-fw?branch=main) - [help with code coverage](https://github.com/bots-go-framework/bots-fw/issues/64) needed. +[![Coverage Status](https://coveralls.io/repos/github/bots-go-framework/bots-fw/badge.svg?branch=main)](https://coveralls.io/github/bots-go-framework/bots-fw?branch=main) - [help with code coverage](https://github.com/bots-go-framework/bots-fw/issues/64) +needed. ## 🍿 Usage @@ -47,36 +50,54 @@ with usage of [dalgo](https://github.com/dal-go) library. } ## πŸ€– Sample bots built with Strongo Bots Framework + The best way to learn is to see examples of usage. Here is few: - * ⚫βšͺ [**Reversi** game](https://github.com/prizarena/reversi) - open source game. (*Telegram: [@ReversiGameBot](https://t.me/ReversiGameBot)*) - * βœ–οΈβ­• [**Bidding Tic-Tac-Toe**](https://github.com/prizarena/bidding-tictactoe) - open source game. (*Telegram: [@BiddingTicTacToeBot](https://t.me/BiddingTicTacToeBot)*) - * πŸ“ƒβœ‚οΈ [**Rock-Paper-Scissors**](https://github.com/prizarena/rock-paper-scissors) - open source game. (*Telegram: [@playRockPaperScissorsBot](https://t.me/playRockPaperScissorsBot)*) - * πŸ’ΈπŸ“ [**Debtus.app**](http://debtus.app/) β€” a bot & a reminder service that helps to track your debts & credits. + +* ⚫βšͺ [**Reversi** game](https://github.com/prizarena/reversi) - open source game. ( + *Telegram: [@ReversiGameBot](https://t.me/ReversiGameBot)*) +* βœ–οΈβ­• [**Bidding Tic-Tac-Toe**](https://github.com/prizarena/bidding-tictactoe) - open source game. ( + *Telegram: [@BiddingTicTacToeBot](https://t.me/BiddingTicTacToeBot)*) +* πŸ“ƒβœ‚οΈ [**Rock-Paper-Scissors**](https://github.com/prizarena/rock-paper-scissors) - open source game. ( + *Telegram: [@playRockPaperScissorsBot](https://t.me/playRockPaperScissorsBot)*) +* πŸ’ΈπŸ“ [**Debtus.app**](http://debtus.app/) β€” a bot & a reminder service that helps to track your debts & credits. Sends automated reminders to you & your debtors (_in messenger, email, SMS_). We would be happy to place a link to your example / bot that is implemented using this framework. ## πŸ“¦ Go API libraries used by the framework to talk to messengers + You can use any Bot API library by implementing couple of simple interface but the framework comes with few buildins: - * [bots-go-framework/bots-api-telegram](https://github.com/bots-go-framework/bots-api-telegram) - Go library for [**Telegram** Bot API](https://core.telegram.org/bots/api) - * [bots-go-framework/bots-api-fbm](https://github.com/bots-go-framework/bots-api-fbm) - Go library for [**Facebook Messenger** Bot API](https://developers.facebook.com/docs/messenger-platform) - * [bots-go-framework/bots-api-viber](https://github.com/bots-go-framework/bots-api-viber) - Go library for [**Viber** Bot API](https://developers.viber.com/) - + +* [bots-go-framework/bots-api-telegram](https://github.com/bots-go-framework/bots-api-telegram) - Go library for [* + *Telegram** Bot API](https://core.telegram.org/bots/api) +* [bots-go-framework/bots-api-fbm](https://github.com/bots-go-framework/bots-api-fbm) - Go library for [**Facebook + Messenger** Bot API](https://developers.facebook.com/docs/messenger-platform) +* [bots-go-framework/bots-api-viber](https://github.com/bots-go-framework/bots-api-viber) - Go library for [**Viber + ** Bot API](https://developers.viber.com/) + ## πŸ“¦ Other Go libraries used by the bot framework - * [dal-go/dalgo](https://github.com/dal-go/dalgo) - Database abstraction layer (DAL) in Go language - * [strongo/gamp](https://github.com/strongo/gamp) - Golang buffered client for Google Analytics (GA) Measurement Protocol + +* [dal-go/dalgo](https://github.com/dal-go/dalgo) - Database abstraction layer (DAL) in Go language +* [strongo/gamp](https://github.com/strongo/gamp) - Golang buffered client for Google Analytics (GA) Measurement + Protocol ## [Can I use — features cross-table for bot messenger APIs](can-i-use-bots-api.md) + We are building a [cross-table of features](can-i-use-bots-api.md) supported by different bot APIs. ## Database Abstraction Layer (DAL) + Thanks to [dalgo](https://github.com/dal-go) library the framework can work with different databases. - +The Db structure is described in [README-DB.md](README-DB.md). + ## πŸ«‚ Contributors - * [Alexander Trakhimenok](https://ie.linkedin.com/in/alexandertrakhimenok) + +* [Alexander Trakhimenok](https://ie.linkedin.com/in/alexandertrakhimenok) ## πŸ“° Press + There are no articles about the Strongo Bots Framework just yet. Send us a link if you find such. - + ## πŸ“œ [License](https://github.com/strongo/bots-framework/blob/master/LICENSE) + Licensed under Apache 2.0 license diff --git a/botsfw/botsdal/dal_bot.go b/botsfw/botsdal/dal_bot.go index 87b8756..900b3fc 100644 --- a/botsfw/botsdal/dal_bot.go +++ b/botsfw/botsdal/dal_bot.go @@ -7,5 +7,8 @@ const botsCollection = "bots" // NewBotKey creates a dalgo key to specific bot record func NewBotKey(platformID, botID string) *dal.Key { platformKey := NewPlatformKey(platformID) + if botID == "" { + panic("botID is required parameter") + } return dal.NewKeyWithParentAndID(platformKey, botsCollection, botID) } diff --git a/botsfw/botsdal/dal_bot_user.go b/botsfw/botsdal/dal_bot_user.go index 216c656..55b4ea3 100644 --- a/botsfw/botsdal/dal_bot_user.go +++ b/botsfw/botsdal/dal_bot_user.go @@ -9,23 +9,46 @@ import ( const botUsersCollection = "botUsers" +// NewBotUserKey creates a dalgo key to specific bot user record func NewBotUserKey(platformID, botID, botUserID string) *dal.Key { botKey := NewBotKey(platformID, botID) + if botUserID == "" { + panic("botUserID is required parameter") + } return dal.NewKeyWithParentAndID(botKey, botUsersCollection, botUserID) } +// GetBotUser loads bot user data func GetBotUser( ctx context.Context, tx dal.ReadSession, - platformID, botID, userID string, + platformID, botID, botUserID string, newData func() botsfwmodels.BotUserData, ) (botUser record.DataWithID[string, botsfwmodels.BotUserData], err error) { - key := NewBotUserKey(platformID, botID, userID) + botUserKey := NewBotUserKey(platformID, botID, botUserID) data := newData() - botUser = record.NewDataWithID(userID, key, data) + botUser = record.NewDataWithID(botUserID, botUserKey, data) return botUser, tx.Get(ctx, botUser.Record) } +// CreateBotUserRecord creates bot user record in database +func CreateBotUserRecord( + ctx context.Context, + tx dal.ReadwriteTransaction, + platformID, botID, botUserID string, + botUserData botsfwmodels.BotUserData, +) (err error) { + if validatableData, ok := botUserData.(interface{ Validate() error }); ok { + if err = validatableData.Validate(); err != nil { + return err + } + } + botUserKey := NewBotUserKey(platformID, botID, botUserID) + botUser := record.NewDataWithID(botUserID, botUserKey, botUserData) + err = tx.Insert(ctx, botUser.Record) + return err +} + //import ( // "context" // "fmt" @@ -114,7 +137,7 @@ func GetBotUser( // }) //} // -//func (store botUserStore) CreateBotUser(c context.Context, botID string, apiUser botsfw.WebhookActor) (botsfwmodels.BotUserData, error) { +//func (store botUserStore) CreateBotUserRecord(c context.Context, botID string, apiUser botsfw.WebhookActor) (botsfwmodels.BotUserData, error) { // return store.createBotUser(c, botID, apiUser) //} // diff --git a/botsfw/botsdal/dal_bot_user_test.go b/botsfw/botsdal/dal_bot_user_test.go index e876b4f..b48debc 100644 --- a/botsfw/botsdal/dal_bot_user_test.go +++ b/botsfw/botsdal/dal_bot_user_test.go @@ -1,21 +1,25 @@ package botsdal import ( + "context" + "github.com/bots-go-framework/bots-fw-store/botsfwmodels" + "github.com/dal-go/dalgo/dal" + "github.com/dal-go/dalgo/record" "testing" ) -func TestNewBotUserStore(t *testing.T) { +func TestGetBotUser(t *testing.T) { type args struct { //collection string - //platform string - //db DbProvider - //newBotUserData func(botID string) (botsfwmodels.BotUserData, error) - //createNewUser BotUserCreator + platform string + botID string + botUserID string } tests := []struct { name string args args shouldPanic bool + checkResult func(botUser record.DataWithID[string, botsfwmodels.BotUserData], err error) }{ {name: "empty", shouldPanic: true}, } @@ -28,8 +32,48 @@ func TestNewBotUserStore(t *testing.T) { } }() } - panic("temporary disabled") - //_ = newBotUserStore(tt.args.collection, tt.args.platform, tt.args.db, tt.args.newBotUserData, tt.args.createNewUser) + var tx dal.ReadwriteTransaction + botUser, err := GetBotUser(nil, tx, tt.args.platform, tt.args.botID, tt.args.botUserID, func() botsfwmodels.BotUserData { + return nil + }) + tt.checkResult(botUser, err) + }) + } +} + +func TestCreateBotUserRecord(t *testing.T) { + type args struct { + platform string + botID string + botUserID string + botUserData botsfwmodels.BotUserData + } + tests := []struct { + name string + args args + shouldPanic bool + checkResult func(err error) + }{ + { + name: "empty", + shouldPanic: true, + checkResult: func(err error) { + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + if tt.shouldPanic { + defer func() { + if r := recover(); r == nil { + t.Errorf("CreateBotUserRecord() did not panic") + } + }() + } + var tx dal.ReadwriteTransaction + err := CreateBotUserRecord(ctx, tx, tt.args.platform, tt.args.botID, tt.args.botUserID, tt.args.botUserData) + tt.checkResult(err) }) } } diff --git a/botsfw/botsdal/dal_platform.go b/botsfw/botsdal/dal_platform.go index 10ab18e..0952a4e 100644 --- a/botsfw/botsdal/dal_platform.go +++ b/botsfw/botsdal/dal_platform.go @@ -5,5 +5,8 @@ import "github.com/dal-go/dalgo/dal" const botPlatformsCollection = "botPlatforms" func NewPlatformKey(platform string) *dal.Key { + if platform == "" { + panic("platform is required parameter") + } return dal.NewKeyWithID(botPlatformsCollection, platform) } diff --git a/botsfw/webhook_context.go b/botsfw/webhook_context.go index 2feb8d1..4fb99bd 100644 --- a/botsfw/webhook_context.go +++ b/botsfw/webhook_context.go @@ -85,8 +85,8 @@ type WebhookContext interface { // TODO: Make interface much smaller? AppUserID() string - // AppUserInt64ID TODO: Deprecate: use AppUserID() instead - AppUserInt64ID() int64 + // AppUserInt64ID Deprecate: use AppUserID() instead + //AppUserInt64ID() int64 AppUserData() (botsfwmodels.AppUserData, error) //SaveAppUser(appUserID int64, appUserEntity BotAppUser) error diff --git a/botsfw/webhook_context_base.go b/botsfw/webhook_context_base.go index 4bd606b..ba184a7 100644 --- a/botsfw/webhook_context_base.go +++ b/botsfw/webhook_context_base.go @@ -12,7 +12,6 @@ import ( "github.com/strongo/strongoapp" "net/http" "net/url" - "strconv" "strings" "time" ) @@ -26,11 +25,11 @@ type whContextDummy struct { } func (w whContextDummy) NewEditMessage(text string, format MessageFormat) (MessageFromBot, error) { - panic("must be implemented in platform specific code") + panic(fmt.Sprintf("must be implemented in platform specific code: text=%s, format=%v", text, format)) } func (w whContextDummy) UpdateLastProcessed(chatEntity botsfwmodels.BotChatData) error { - panic("implement me in WebhookContextBase") //TODO + panic(fmt.Sprintf("implement me in WebhookContextBase - UpdateLastProcessed(chatEntity=%v)", chatEntity)) } func (w whContextDummy) AppUserData() (botsfwmodels.AppUserData, error) { @@ -38,7 +37,7 @@ func (w whContextDummy) AppUserData() (botsfwmodels.AppUserData, error) { } func (w whContextDummy) IsNewerThen(chatEntity botsfwmodels.BotChatData) bool { - panic("implement me in WebhookContextBase") //TODO + panic(fmt.Sprintf("implement me in WebhookContextBase - IsNewerThen(chatEntity=%v)", chatEntity)) } func (w whContextDummy) Responder() WebhookResponder { @@ -49,7 +48,7 @@ func (w whContextDummy) Responder() WebhookResponder { // WebhookContextBase provides base implementation of WebhookContext interface // TODO: Document purpose of a dedicated base struct (e.g. example of usage by developers) type WebhookContextBase struct { - //w http.ResponseWriter + //w http.ResponseWriter r *http.Request c context.Context botAppContext BotAppContext @@ -65,7 +64,7 @@ type WebhookContextBase struct { locale i18n.Locale - // At the moment there is no reason to expose botChat record publicly + // At the moment, there is no reason to expose botChat record publicly // If there is some it should be documented with a use case botChat record.DataWithID[string, botsfwmodels.BotChatData] botUser record.DataWithID[string, botsfwmodels.BotUserData] // Telegram user ID is an integer, but we keep it as a string for consistency & simplicity. @@ -216,16 +215,16 @@ func (whcb *WebhookContextBase) BotChatID() (botChatID string, err error) { return whcb.botChat.ID, nil } -// AppUserInt64ID: TODO: Deprecate: use AppUserID() instead -func (whcb *WebhookContextBase) AppUserInt64ID() (appUserID int64) { - if s := whcb.AppUserID(); s != "" { - var err error - if appUserID, err = strconv.ParseInt(s, 10, 64); err != nil { - panic(fmt.Errorf("failed to parse app user ID %v: %w", s, err)) - } - } - return appUserID -} +// AppUserInt64ID Deprecate: use AppUserID() instead +//func (whcb *WebhookContextBase) AppUserInt64ID() (appUserID int64) { +// if s := whcb.AppUserID(); s != "" { +// var err error +// if appUserID, err = strconv.ParseInt(s, 10, 64); err != nil { +// panic(fmt.Errorf("failed to parse app user ID %v: %w", s, err)) +// } +// } +// return appUserID +//} // AppUserID return current app user ID as a string. AppUserIntID() is deprecated. func (whcb *WebhookContextBase) AppUserID() (appUserID string) { @@ -494,8 +493,14 @@ func (whcb *WebhookContextBase) ChatData() botsfwmodels.BotChatData { botUserID := fmt.Sprintf("%v", sender.GetID()) appUserID := whcb.AppUserID() webhookChat := whcb.Chat() - isAccessGranted := true // TODO: Implement!!! - if err = whcb.recordsFieldsSetter.SetBotChatFields(whcb.botChat.Data, webhookChat, botID, botUserID, appUserID, isAccessGranted); err != nil { + if err = whcb.recordsFieldsSetter.SetBotChatFields( + whcb.botChat.Data, + webhookChat, + botID, + botUserID, + appUserID, + true, // isAccessGranted - TODO: Implement!!! + ); err != nil { panic(fmt.Errorf("failed to call whcb.recordsMaker.MakeBotChatDto(): %w", err)) } } else {