Skip to content

Commit 944d8a6

Browse files
authored
Merge pull request #8 from pyama86/support-dynamodb
Support dynamodb
2 parents 81e8f38 + 10834d2 commit 944d8a6

File tree

17 files changed

+758
-77
lines changed

17 files changed

+758
-77
lines changed

.github/workflows/ci.yml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,17 @@ jobs:
2424
- name: Install dependencies
2525
run: go mod download
2626

27-
- name: Run tests
27+
- name: Test for sqlite
2828
run: go test -v ./...
29+
30+
- name: Test for dynamodb
31+
run: go test -v ./...
32+
env:
33+
DB_DRIVER: dynamodb
34+
DYNAMO_LOCAL: 1
35+
DYNAMO_TABLE_NAME_PREFIX: test
36+
services:
37+
dynamodb:
38+
image: amazon/dynamodb-local:latest
39+
ports:
40+
- 8000:8000

Makefile

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
LINTER := golangci-lint
2+
.PHONY: lint fmt ci test devdeps
3+
up:
4+
docker compose up -d
5+
6+
down:
7+
docker compose down
8+
docker compose rm -f
9+
10+
clean:
11+
rm -rf docker/dynamodb
12+
13+
reset: down clean up
14+
15+
ci: devdeps lint test
16+
lint:
17+
@echo ">> Running linter ($(LINTER))"
18+
$(LINTER) run
19+
20+
fmt:
21+
@echo ">> Formatting code"
22+
gofmt -w .
23+
goimports -w .
24+
25+
test:
26+
@echo ">> Running tests"
27+
go test -v -cover ./...
28+
29+
devdeps:
30+
@echo ">> Installing development dependencies"
31+
which goimports > /dev/null || go install golang.org/x/tools/cmd/goimports@latest
32+
which golangci-lint > /dev/null || go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

bot/bot.go

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package bot
22

33
import (
4+
"context"
45
"fmt"
56
"log"
67
"log/slog"
8+
"plusplusbot/infra/repository"
79
"regexp"
810

911
"github.com/slack-go/slack"
@@ -27,22 +29,15 @@ type Bot struct {
2729
socketClient *socketmode.Client
2830
verbose bool
2931
logger *slog.Logger
30-
db Database
32+
repo repository.UserPointsRepository
3133
}
3234

3335
// New creates a new Slack bot instance
34-
func New(botToken, appToken, dbConnStr string, verbose bool, logger *slog.Logger) (*Bot, error) {
36+
func New(botToken, appToken string, repo repository.UserPointsRepository, verbose bool, logger *slog.Logger) (*Bot, error) {
3537
if botToken == "" || appToken == "" {
3638
return nil, fmt.Errorf("SLACK_BOT_TOKEN or SLACK_APP_TOKEN is not set")
3739
}
3840

39-
// Initialize database
40-
db, err := DatabaseNew(dbConnStr, logger)
41-
if err != nil {
42-
return nil, fmt.Errorf("failed to initialize database: %w", err)
43-
}
44-
logger.Info("Database connected")
45-
4641
api := slack.New(
4742
botToken,
4843
slack.OptionAppLevelToken(appToken),
@@ -63,7 +58,7 @@ func New(botToken, appToken, dbConnStr string, verbose bool, logger *slog.Logger
6358
socketClient: socketClient,
6459
verbose: verbose,
6560
logger: logger,
66-
db: db,
61+
repo: repo,
6762
}, nil
6863
}
6964

@@ -172,13 +167,14 @@ func (b *Bot) handlePointChangeMessage(ev *slackevents.MessageEvent, operation P
172167
}
173168

174169
// Add points to the user
175-
if err := b.db.AddPoints(userID, pointsChange, is_user); err != nil {
170+
ctx := context.Background()
171+
if err := b.repo.AddPoints(ctx, userID, pointsChange, is_user); err != nil {
176172
b.logger.Error("Error adding points", "error", err)
177173
return
178174
}
179175

180176
// Get current points
181-
points, err := b.db.GetPoints(userID)
177+
points, err := b.repo.GetPoints(ctx, userID)
182178
if err != nil {
183179
b.logger.Error("Error getting points", "error", err)
184180
return
@@ -209,7 +205,8 @@ func (b *Bot) handlePointCheckMessage(ev *slackevents.MessageEvent) {
209205
return
210206
}
211207

212-
points, err := b.db.GetPoints(userID)
208+
ctx := context.Background()
209+
points, err := b.repo.GetPoints(ctx, userID)
213210
if err != nil {
214211
b.logger.Error("Error getting points", "error", err)
215212
return

bot/bot_test.go

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66

77
"log/slog"
8+
"plusplusbot/infra/repository"
89
)
910

1011
func setupTestBot(t *testing.T) (*Bot, func()) {
@@ -22,8 +23,17 @@ func setupTestBot(t *testing.T) (*Bot, func()) {
2223
Level: slog.LevelError,
2324
}))
2425

26+
// Create a SQLite repository for testing
27+
repo, err := repository.NewSQLiteRepository(tempFile.Name(), logger)
28+
if err != nil {
29+
if err := os.Remove(tempFile.Name()); err != nil {
30+
t.Logf("Failed to remove temp file: %v", err)
31+
}
32+
t.Fatalf("Failed to create test repository: %v", err)
33+
}
34+
2535
// Create a test bot with dummy tokens
26-
bot, err := New("dummy-bot-token", "dummy-app-token", tempFile.Name(), false, logger)
36+
bot, err := New("dummy-bot-token", "dummy-app-token", repo, false, logger)
2737
if err != nil {
2838
if err := os.Remove(tempFile.Name()); err != nil {
2939
t.Logf("Failed to remove temp file: %v", err)
@@ -33,7 +43,7 @@ func setupTestBot(t *testing.T) (*Bot, func()) {
3343

3444
// Return cleanup function
3545
cleanup := func() {
36-
if err := bot.db.(*SQLiteDB).db.Close(); err != nil {
46+
if err := repo.Close(); err != nil {
3747
t.Logf("Failed to close database: %v", err)
3848
}
3949
if err := os.Remove(tempFile.Name()); err != nil {
@@ -49,43 +59,51 @@ func TestNew(t *testing.T) {
4959
Level: slog.LevelError,
5060
}))
5161

62+
// Create a real SQLite repository for testing
63+
repo, err := repository.NewSQLiteRepository(":memory:", logger)
64+
if err != nil {
65+
t.Fatalf("Failed to create test repository: %v", err)
66+
}
67+
defer func() {
68+
err := repo.Close()
69+
if err != nil {
70+
t.Fatalf("Failed to close test repository: %v", err)
71+
}
72+
}()
73+
5274
tests := []struct {
53-
name string
54-
botToken string
55-
appToken string
56-
dbConnStr string
57-
verbose bool
58-
wantErr bool
75+
name string
76+
botToken string
77+
appToken string
78+
verbose bool
79+
wantErr bool
5980
}{
6081
{
61-
name: "Valid tokens",
62-
botToken: "valid-bot-token",
63-
appToken: "valid-app-token",
64-
dbConnStr: ":memory:",
65-
verbose: false,
66-
wantErr: false,
82+
name: "Valid tokens",
83+
botToken: "valid-bot-token",
84+
appToken: "valid-app-token",
85+
verbose: false,
86+
wantErr: false,
6787
},
6888
{
69-
name: "Empty bot token",
70-
botToken: "",
71-
appToken: "valid-app-token",
72-
dbConnStr: ":memory:",
73-
verbose: false,
74-
wantErr: true,
89+
name: "Empty bot token",
90+
botToken: "",
91+
appToken: "valid-app-token",
92+
verbose: false,
93+
wantErr: true,
7594
},
7695
{
77-
name: "Empty app token",
78-
botToken: "valid-bot-token",
79-
appToken: "",
80-
dbConnStr: ":memory:",
81-
verbose: false,
82-
wantErr: true,
96+
name: "Empty app token",
97+
botToken: "valid-bot-token",
98+
appToken: "",
99+
verbose: false,
100+
wantErr: true,
83101
},
84102
}
85103

86104
for _, tt := range tests {
87105
t.Run(tt.name, func(t *testing.T) {
88-
bot, err := New(tt.botToken, tt.appToken, tt.dbConnStr, tt.verbose, logger)
106+
bot, err := New(tt.botToken, tt.appToken, repo, tt.verbose, logger)
89107
if (err != nil) != tt.wantErr {
90108
t.Errorf("New() error = %v, wantErr %v", err, tt.wantErr)
91109
}

bot/db_adapter.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package bot
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
7+
"plusplusbot/infra/repository"
8+
)
9+
10+
// Database interface defines the methods for database operations
11+
// This is kept for backward compatibility
12+
type Database interface {
13+
AddPoints(userID string, points int, is_user bool) error
14+
GetPoints(userID string) (int, error)
15+
}
16+
17+
// RepositoryAdapter adapts the UserPointsRepository to the Database interface
18+
type RepositoryAdapter struct {
19+
repo repository.UserPointsRepository
20+
logger *slog.Logger
21+
}
22+
23+
// NewRepositoryAdapter creates a new RepositoryAdapter
24+
func NewRepositoryAdapter(repo repository.UserPointsRepository, logger *slog.Logger) *RepositoryAdapter {
25+
return &RepositoryAdapter{
26+
repo: repo,
27+
logger: logger,
28+
}
29+
}
30+
31+
// AddPoints adds points to a user
32+
func (a *RepositoryAdapter) AddPoints(userID string, points int, is_user bool) error {
33+
ctx := context.Background()
34+
return a.repo.AddPoints(ctx, userID, points, is_user)
35+
}
36+
37+
// GetPoints gets the current points for a user
38+
func (a *RepositoryAdapter) GetPoints(userID string) (int, error) {
39+
ctx := context.Background()
40+
return a.repo.GetPoints(ctx, userID)
41+
}

docker-compose.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
version: '3.8'
2+
services:
3+
dynamodb-local:
4+
command: "-jar DynamoDBLocal.jar -sharedDb -dbPath ./data"
5+
image: "amazon/dynamodb-local:latest"
6+
container_name: dynamodb-local
7+
ports:
8+
- "8000:8000"
9+
volumes:
10+
- "./docker/dynamodb:/home/dynamodblocal/data"
11+
working_dir: /home/dynamodblocal
12+
dynamodb-admin:
13+
container_name: dynamodb-admin
14+
image: aaronshaf/dynamodb-admin:latest
15+
environment:
16+
- DYNAMO_ENDPOINT=http://dynamodb-local:8000
17+
ports:
18+
- 8001:8001
19+
depends_on:
20+
- dynamodb-local

go.mod

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,29 @@ require (
88
)
99

1010
require (
11+
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
12+
github.com/aws/aws-sdk-go-v2/config v1.29.14 // indirect
13+
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect
14+
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.11 // indirect
15+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
16+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
17+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
18+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
19+
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.43.1 // indirect
20+
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.4 // indirect
21+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
22+
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.15 // indirect
23+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
24+
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect
25+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect
26+
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect
27+
github.com/aws/smithy-go v1.22.2 // indirect
28+
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
1129
github.com/gorilla/websocket v1.4.2 // indirect
30+
github.com/guregu/dynamo/v2 v2.3.0 // indirect
1231
github.com/ncruces/julianday v1.0.0 // indirect
1332
github.com/stretchr/testify v1.8.4 // indirect
1433
github.com/tetratelabs/wazero v1.9.0 // indirect
34+
golang.org/x/sync v0.14.0 // indirect
1535
golang.org/x/sys v0.33.0 // indirect
1636
)

go.sum

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,39 @@
1+
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
2+
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
3+
github.com/aws/aws-sdk-go-v2/config v1.29.14 h1:f+eEi/2cKCg9pqKBoAIwRGzVb70MRKqWX4dg1BDcSJM=
4+
github.com/aws/aws-sdk-go-v2/config v1.29.14/go.mod h1:wVPHWcIFv3WO89w0rE10gzf17ZYy+UVS1Geq8Iei34g=
5+
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM=
6+
github.com/aws/aws-sdk-go-v2/credentials v1.17.67/go.mod h1:p3C44m+cfnbv763s52gCqrjaqyPikj9Sg47kUVaNZQQ=
7+
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.11 h1:KUHQows9JhDp+RJRs9KLN+ljsK5D+oLV13Wr/TwlSr4=
8+
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.14.11/go.mod h1:4kdmcGnKW4R9l2ddj6hNgKnJoxztjvJNCoI9eikMgvI=
9+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
10+
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
11+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=
12+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY=
13+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0=
14+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=
15+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
16+
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
17+
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.43.1 h1:YYjNTAyPL0425ECmq6Xm48NSXdT6hDVQmLOJZxyhNTM=
18+
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.43.1/go.mod h1:yYaWRnVSPyAmexW5t7G3TcuYoalYfT+xQwzWsvtUQ7M=
19+
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.4 h1:qOvCqaiLTc0MnIdZr0LbdtJKetiRscHxi+9XjjtlEAs=
20+
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.22.4/go.mod h1:3YxVsEoCNYOLIbdA+cCXSp1fom9hrhyB1DsCiYryCaQ=
21+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
22+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
23+
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.15 h1:M1R1rud7HzDrfCdlBQ7NjnRsDNEhXO/vGhuD189Ggmk=
24+
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.15/go.mod h1:uvFKBSq9yMPV4LGAi7N4awn4tLY+hKE35f8THes2mzQ=
25+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
26+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=
27+
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2cL6c9cuKV2Ps+G8=
28+
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
29+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako=
30+
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
31+
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/XvaX32evhproijJEZY=
32+
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
33+
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
34+
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
35+
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
36+
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
137
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
238
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
339
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
@@ -6,6 +42,8 @@ github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
642
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
743
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
844
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
45+
github.com/guregu/dynamo/v2 v2.3.0 h1:WN3G6UTyX+clTzQeKzm2IenKkO2VUXpZN8QQc58IDtI=
46+
github.com/guregu/dynamo/v2 v2.3.0/go.mod h1:fUKI2LycE+efoMAdgLvAtleD02KgrQUN0tfm39Q2mmI=
947
github.com/ncruces/go-sqlite3 v0.25.2 h1:suu3C7y92hPqozqO8+w3K333Q1VhWyN6K3JJKXdtC2U=
1048
github.com/ncruces/go-sqlite3 v0.25.2/go.mod h1:46HIzeCQQ+aNleAxCli+vpA2tfh7ttSnw24kQahBc1o=
1149
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
@@ -19,6 +57,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
1957
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
2058
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
2159
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
60+
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
61+
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
2262
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
2363
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
2464
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=

0 commit comments

Comments
 (0)