Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invitations feature #7

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- main
- feat/invitations

jobs:
build-upload-deploy:
Expand Down
2 changes: 2 additions & 0 deletions bot/common/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/PaulSonOfLars/gotgbot/v2/ext/handlers/filters/callbackquery"
"github.com/PaulSonOfLars/gotgbot/v2/ext/handlers/filters/message"
"github.com/aws/aws-lambda-go/events"
"github.com/bugfloyd/anonymous-telegram-bot/common/invitations"
"github.com/bugfloyd/anonymous-telegram-bot/secrets"
"log"
"net/http"
Expand Down Expand Up @@ -39,6 +40,7 @@ func InitBot(request APIRequest) (APIResponse, error) {
MaxRoutines: ext.DefaultMaxRoutines,
})

invitations.InitInvitations(dispatcher)
rootHandler := NewRootHandler()

// Commands
Expand Down
76 changes: 76 additions & 0 deletions bot/common/invitations/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package invitations

import (
"github.com/PaulSonOfLars/gotgbot/v2"
"github.com/PaulSonOfLars/gotgbot/v2/ext"
"github.com/PaulSonOfLars/gotgbot/v2/ext/handlers"
"github.com/PaulSonOfLars/gotgbot/v2/ext/handlers/filters/callbackquery"
"github.com/bugfloyd/anonymous-telegram-bot/common/users"
)

type User struct {
ItemID string `dynamo:",hash" index:"UserUUID-GSI,range"`
UserUUID string `index:"UserUUID-GSI,hash"`
InvitationsLeft uint32
InvitationsUsed uint32
Type string
}

type Invitation struct {
ItemID string `dynamo:",hash" index:"UserUUID-GSI,range"`
UserUUID string `index:"UserUUID-GSI,hash"`
InvitationsLeft uint32
InvitationsUsed uint32
}

const (
GeneratingInvitationState users.State = "GENERATING_INVITATION"
SendingInvitationCodeState users.State = "SENDING_INVITATION_CODE"
)

const (
InviteCommand Command = "invite"
RegisterCommand Command = "register"
)

const (
GenerateInvitationCallback CallbackCommand = "generate-invitation-callback"
CancelSendingInvitationCodeCallback CallbackCommand = "cancel-invitation-code-callback"
)

func InitInvitations(dispatcher *ext.Dispatcher) {
rootHandler := NewRootHandler()

// Commands
dispatcher.AddHandler(handlers.NewCommand(string(InviteCommand), rootHandler.init(InviteCommand)))
dispatcher.AddHandler(handlers.NewCommand(string(RegisterCommand), rootHandler.init(RegisterCommand)))

// Callbacks
dispatcher.AddHandler(handlers.NewCallback(callbackquery.Prefix("inv|g"), rootHandler.init(GenerateInvitationCallback)))
dispatcher.AddHandler(handlers.NewCallback(callbackquery.Prefix("inv|reg|c"), rootHandler.init(CancelSendingInvitationCodeCallback)))
}

func ProcessText(b *gotgbot.Bot, ctx *ext.Context) (bool, error) {
rh := NewRootHandler()
err := rh.HandleUserAndRepos(ctx)
if err != nil {
return false, err
}

switch rh.user.State {
case GeneratingInvitationState:
err = rh.GenerateInvitation(b, ctx)
if err != nil {
return true, err
}
return true, nil
case SendingInvitationCodeState:
err = rh.ValidateCode(b, ctx)
if err != nil {
return true, err
}
return true, nil
default:
return false, nil
}
}
145 changes: 145 additions & 0 deletions bot/common/invitations/repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package invitations

import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/guregu/dynamo"
"os"
"reflect"
)

type Repository struct {
table dynamo.Table
}

func NewRepository() (*Repository, error) {
var sess *session.Session
customDynamoDbEndpoint := os.Getenv("DYNAMODB_ENDPOINT")
awsRegion := os.Getenv("AWS_REGION")

if customDynamoDbEndpoint != "" {
sess = session.Must(session.NewSession(&aws.Config{
Region: aws.String(awsRegion),
Endpoint: aws.String(customDynamoDbEndpoint),
}))
} else {
sess = session.Must(session.NewSession(&aws.Config{Region: aws.String(awsRegion)}))
}

db := dynamo.New(sess)

return &Repository{
table: db.Table("AnonymousBot_Invitations"),
}, nil
}

func (repo *Repository) createUser(userUUID string) (*User, error) {
i := User{
ItemID: "USER#" + userUUID,
UserUUID: userUUID,
InvitationsLeft: 0,
InvitationsUsed: 0,
Type: "NORMAL",
}
err := repo.table.Put(i).Run()
if err != nil {
return nil, fmt.Errorf("invitations: failed to create user: %w", err)
}
return &i, nil
}

func (repo *Repository) createInvitation(code string, userUUID string, count uint32) (*Invitation, error) {
i := Invitation{
ItemID: "INVITATION#" + code,
UserUUID: userUUID,
InvitationsLeft: count,
InvitationsUsed: 0,
}
err := repo.table.Put(i).Run()
if err != nil {
return nil, fmt.Errorf("failed to create invitation: %w", err)
}
return &i, nil
}

func (repo *Repository) readUser(userUUID string) (*User, error) {
var u User
err := repo.table.Get("ItemID", "USER#"+userUUID).One(&u)
if err != nil {
return nil, fmt.Errorf("failed to get user: %w", err)
}
return &u, nil
}

func (repo *Repository) readInvitation(code string) (*Invitation, error) {
var u Invitation
err := repo.table.Get("ItemID", "INVITATION#"+code).One(&u)
if err != nil {
return nil, fmt.Errorf("failed to get invitation by id: %w", err)
}
return &u, nil
}

func (repo *Repository) readInvitationsByUser(userUUID string) (*[]Invitation, error) {
var invitation []Invitation
err := repo.table.Get("UserUUID", userUUID).Index("UserUUID-GSI").Range("ItemID", dynamo.BeginsWith, "INVITATION").All(&invitation)
if err != nil {
return nil, fmt.Errorf("failed to get invitations by user: %w", err)
}
return &invitation, nil
}

func (repo *Repository) updateUser(user *User, updates map[string]interface{}) error {
updateBuilder := repo.table.Update("ItemID", user.ItemID)
for key, value := range updates {
updateBuilder = updateBuilder.Set(key, value)
}
err := updateBuilder.Run()
if err != nil {
return fmt.Errorf("failed to update user: %w", err)
}

// Reflecting on user to update fields based on updates map
val := reflect.ValueOf(user).Elem() // We use .Elem() to dereference the pointer to user
for key, value := range updates {
fieldVal := val.FieldByName(key)
if fieldVal.IsValid() && fieldVal.CanSet() {
// Ensure the value is of the correct type
correctTypeValue := reflect.ValueOf(value)
if correctTypeValue.Type().ConvertibleTo(fieldVal.Type()) {
correctTypeValue = correctTypeValue.Convert(fieldVal.Type())
}
fieldVal.Set(correctTypeValue)
}
}

return nil
}

func (repo *Repository) updateInvitation(invitation *Invitation, updates map[string]interface{}) error {
updateBuilder := repo.table.Update("ItemID", invitation.ItemID)
for key, value := range updates {
updateBuilder = updateBuilder.Set(key, value)
}
err := updateBuilder.Run()
if err != nil {
return fmt.Errorf("failed to update invitation: %w", err)
}

// Reflecting on user to update fields based on updates map
val := reflect.ValueOf(invitation).Elem() // We use .Elem() to dereference the pointer to user
for key, value := range updates {
fieldVal := val.FieldByName(key)
if fieldVal.IsValid() && fieldVal.CanSet() {
// Ensure the value is of the correct type
correctTypeValue := reflect.ValueOf(value)
if correctTypeValue.Type().ConvertibleTo(fieldVal.Type()) {
correctTypeValue = correctTypeValue.Convert(fieldVal.Type())
}
fieldVal.Set(correctTypeValue)
}
}

return nil
}
Loading
Loading