Skip to content

Commit edfa84a

Browse files
committed
Switch to Fiber and HTMX
1 parent 97e3723 commit edfa84a

32 files changed

+1781
-67
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build:
44

55
.PHONY: run
66
run:
7-
go run
7+
go run ./...
88

99
.PHONY: lint
1010
lint:

README.md

+10-9
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
[![Go Report Card](https://goreportcard.com/badge/github.com/cloudlena/s3manager)](https://goreportcard.com/report/github.com/cloudlena/s3manager)
44
[![Build Status](https://github.com/cloudlena/s3manager/actions/workflows/main.yml/badge.svg)](https://github.com/cloudlena/s3manager/actions)
55

6-
A Web GUI written in Go to manage S3 buckets from any provider.
6+
A Web GUI to manage S3 buckets from any provider.
77

88
![Screenshot](https://raw.githubusercontent.com/cloudlena/s3manager/master/screenshot.png)
99

10+
:rocket: Powered by [Fiber](https://gofiber.io/) and [HTMX](https://htmx.org/)
11+
1012
## Features
1113

1214
- List all buckets in your account
@@ -29,28 +31,27 @@ The application can be configured with the following environment variables:
2931
- `USE_SSL`: Whether your S3 server uses SSL or not (defaults to `true`)
3032
- `SKIP_SSL_VERIFICATION`: Whether the HTTP client should skip SSL verification (defaults to `false`)
3133
- `SIGNATURE_TYPE`: The signature type to be used (defaults to `V4`; valid values are `V2, V4, V4Streaming, Anonymous`)
32-
- `PORT`: The port the s3manager app should listen on (defaults to `8080`)
34+
- `PORT`: The port the `s3manager` app should listen on (defaults to `8080`)
3335
- `ALLOW_DELETE`: Enable buttons to delete objects (defaults to `true`)
3436
- `FORCE_DOWNLOAD`: Add response headers for object downloading instead of opening in a new tab (defaults to `true`)
3537
- `LIST_RECURSIVE`: List all objects in buckets recursively (defaults to `false`)
3638
- `USE_IAM`: Use IAM role instead of key pair (defaults to `false`)
3739
- `IAM_ENDPOINT`: Endpoint for IAM role retrieving (Can be blank for AWS)
38-
- `SSE_TYPE`: Specified server side encrpytion (defaults blank) Valid values can be `SSE`, `KMS`, `SSE-C` all others values don't enable the SSE
40+
- `SSE_TYPE`: Specified server side encryption (defaults blank) Valid values can be `SSE`, `KMS`, `SSE-C` all others values don't enable the SSE
3941
- `SSE_KEY`: The key needed for SSE method (only for `KMS` and `SSE-C`)
40-
- `TIMEOUT`: The read and write timout in seconds (default to `600` - 10 minutes)
42+
- `TIMEOUT`: The read and write timeout in seconds (default to `600` - 10 minutes)
4143

42-
### Build and Run Locally
44+
### Run Locally
4345

44-
1. Run `make build`
45-
1. Execute the created binary and visit <http://localhost:8080>
46+
1. Run `make run`
4647

4748
### Run Container image
4849

4950
1. Run `docker run -p 8080:8080 -e 'ACCESS_KEY_ID=XXX' -e 'SECRET_ACCESS_KEY=xxx' cloudlena/s3manager`
5051

5152
### Deploy to Kubernetes
5253

53-
You can deploy s3manager to a Kubernetes cluster using the [Helm chart](https://github.com/sergeyshevch/s3manager-helm).
54+
You can deploy `s3manager` to a Kubernetes cluster using the [Helm chart](https://github.com/sergeyshevch/s3manager-helm).
5455

5556
## Development
5657

@@ -70,7 +71,7 @@ The image is available on [Docker Hub](https://hub.docker.com/r/cloudlena/s3mana
7071

7172
### Run Locally for Testing
7273

73-
There is an example [docker-compose.yml](https://github.com/cloudlena/s3manager/blob/master/docker-compose.yml) file that spins up an S3 service and the s3manager. You can try it by issuing the following command:
74+
There is an example [docker-compose.yml](https://github.com/cloudlena/s3manager/blob/master/docker-compose.yml) file that spins up an S3 service and the `s3manager`. You can try it by issuing the following command:
7475

7576
```shell
7677
$ docker-compose up

go.mod

+12-1
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,36 @@ module github.com/cloudlena/s3manager
33
go 1.21.3
44

55
require (
6-
github.com/cloudlena/adapters v0.0.0-20231026063741-fb9c991fff9e
6+
github.com/gofiber/fiber/v2 v2.51.0
7+
github.com/gofiber/template/html/v2 v2.0.5
78
github.com/gorilla/mux v1.8.1
89
github.com/matryer/is v1.4.1
910
github.com/minio/minio-go/v7 v7.0.63
1011
github.com/spf13/viper v1.17.0
1112
)
1213

1314
require (
15+
github.com/andybalholm/brotli v1.0.6 // indirect
1416
github.com/dustin/go-humanize v1.0.1 // indirect
1517
github.com/fsnotify/fsnotify v1.7.0 // indirect
18+
github.com/gofiber/template v1.8.2 // indirect
19+
github.com/gofiber/utils v1.1.0 // indirect
1620
github.com/google/uuid v1.4.0 // indirect
1721
github.com/hashicorp/hcl v1.0.0 // indirect
1822
github.com/json-iterator/go v1.1.12 // indirect
1923
github.com/klauspost/compress v1.17.2 // indirect
2024
github.com/klauspost/cpuid/v2 v2.2.6 // indirect
2125
github.com/magiconair/properties v1.8.7 // indirect
26+
github.com/mattn/go-colorable v0.1.13 // indirect
27+
github.com/mattn/go-isatty v0.0.20 // indirect
28+
github.com/mattn/go-runewidth v0.0.15 // indirect
2229
github.com/minio/md5-simd v1.1.2 // indirect
2330
github.com/minio/sha256-simd v1.0.1 // indirect
2431
github.com/mitchellh/mapstructure v1.5.0 // indirect
2532
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
2633
github.com/modern-go/reflect2 v1.0.2 // indirect
2734
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
35+
github.com/rivo/uniseg v0.4.4 // indirect
2836
github.com/rs/xid v1.5.0 // indirect
2937
github.com/sagikazarmark/locafero v0.3.0 // indirect
3038
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
@@ -34,6 +42,9 @@ require (
3442
github.com/spf13/cast v1.5.1 // indirect
3543
github.com/spf13/pflag v1.0.5 // indirect
3644
github.com/subosito/gotenv v1.6.0 // indirect
45+
github.com/valyala/bytebufferpool v1.0.0 // indirect
46+
github.com/valyala/fasthttp v1.51.0 // indirect
47+
github.com/valyala/tcplisten v1.0.0 // indirect
3748
go.uber.org/multierr v1.11.0 // indirect
3849
golang.org/x/crypto v0.15.0 // indirect
3950
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect

go.sum

+28-2
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
3838
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
3939
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
4040
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
41+
github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI=
42+
github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
4143
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
4244
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
4345
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
4446
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
4547
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
46-
github.com/cloudlena/adapters v0.0.0-20231026063741-fb9c991fff9e h1:ivHfzlfxOt0tNXk3iDXz2EEnRG0fZGBwQ+prrouTAP4=
47-
github.com/cloudlena/adapters v0.0.0-20231026063741-fb9c991fff9e/go.mod h1:bf4uqsP0magG7Imj4SH0236acfqt8ZEz+XohCDmZ7Pw=
4848
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
4949
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
5050
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -67,6 +67,14 @@ github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyT
6767
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
6868
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
6969
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
70+
github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ=
71+
github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U=
72+
github.com/gofiber/template v1.8.2 h1:PIv9s/7Uq6m+Fm2MDNd20pAFFKt5wWs7ZBd8iV9pWwk=
73+
github.com/gofiber/template v1.8.2/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8=
74+
github.com/gofiber/template/html/v2 v2.0.5 h1:BKLJ6Qr940NjntbGmpO3zVa4nFNGDCi/IfUiDB9OC20=
75+
github.com/gofiber/template/html/v2 v2.0.5/go.mod h1:RCF14eLeQDCSUPp0IGc2wbSSDv6yt+V54XB/+Unz+LM=
76+
github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
77+
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
7078
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
7179
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
7280
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -156,6 +164,13 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V
156164
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
157165
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
158166
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
167+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
168+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
169+
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
170+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
171+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
172+
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
173+
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
159174
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
160175
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
161176
github.com/minio/minio-go/v7 v7.0.63 h1:GbZ2oCvaUdgT5640WJOpyDhhDxvknAJU2/T3yurwcbQ=
@@ -177,6 +192,9 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
177192
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
178193
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
179194
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
195+
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
196+
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
197+
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
180198
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
181199
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
182200
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
@@ -211,6 +229,12 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU
211229
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
212230
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
213231
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
232+
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
233+
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
234+
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
235+
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
236+
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
237+
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
214238
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
215239
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
216240
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -356,7 +380,9 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
356380
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
357381
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
358382
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
383+
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
359384
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
385+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
360386
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
361387
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
362388
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package s3manager
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/gofiber/fiber/v2"
7+
)
8+
9+
// HandleBucketList renders all buckets as an HTML list.
10+
func (s *S3Manager) HandleBucketList(c *fiber.Ctx) error {
11+
buckets, err := s.s3.ListBuckets(c.Context())
12+
if err != nil {
13+
return fmt.Errorf("error listing buckets: %w", err)
14+
}
15+
16+
return c.Render("bucket-list", fiber.Map{
17+
"Buckets": buckets,
18+
})
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package s3manager_test
2+
3+
import (
4+
"context"
5+
"errors"
6+
"io"
7+
"net/http"
8+
"strings"
9+
"testing"
10+
11+
"github.com/cloudlena/s3manager/internal/s3manager"
12+
"github.com/cloudlena/s3manager/internal/s3manager/mocks"
13+
"github.com/gofiber/fiber/v2"
14+
"github.com/gofiber/template/html/v2"
15+
"github.com/matryer/is"
16+
"github.com/minio/minio-go/v7"
17+
)
18+
19+
func TestHandleBucketList(t *testing.T) {
20+
t.Parallel()
21+
22+
cases := []struct {
23+
it string
24+
listBucketsVal []minio.BucketInfo
25+
listBucketsErr error
26+
expectedStatusCode int
27+
expectedBodyContains string
28+
}{
29+
{
30+
it: "renders a list of buckets",
31+
listBucketsVal: []minio.BucketInfo{{Name: "BUCKET-NAME"}},
32+
expectedStatusCode: http.StatusOK,
33+
expectedBodyContains: "BUCKET-NAME",
34+
},
35+
{
36+
it: "renders placeholder if no buckets",
37+
expectedStatusCode: http.StatusOK,
38+
expectedBodyContains: "No buckets yet",
39+
},
40+
{
41+
it: "returns error if there is an S3 error",
42+
listBucketsErr: errors.New("mocked s3 error"),
43+
expectedStatusCode: http.StatusInternalServerError,
44+
expectedBodyContains: "mocked s3 error",
45+
},
46+
}
47+
48+
for _, tc := range cases {
49+
tc := tc
50+
t.Run(tc.it, func(t *testing.T) {
51+
t.Parallel()
52+
is := is.New(t)
53+
54+
s3 := &mocks.S3Mock{
55+
ListBucketsFunc: func(ctx context.Context) ([]minio.BucketInfo, error) {
56+
return tc.listBucketsVal, tc.listBucketsErr
57+
},
58+
}
59+
server := s3manager.New(s3, true, "", "")
60+
61+
engine := html.New("../../views", ".html.gotmpl")
62+
app := fiber.New(fiber.Config{
63+
Views: engine,
64+
})
65+
app.Get("/bucket-list", server.HandleBucketList)
66+
67+
req, err := http.NewRequest(fiber.MethodGet, "/bucket-list", nil)
68+
is.NoErr(err)
69+
70+
resp, err := app.Test(req)
71+
is.NoErr(err)
72+
defer resp.Body.Close()
73+
body, err := io.ReadAll(resp.Body)
74+
is.NoErr(err)
75+
76+
is.Equal(resp.StatusCode, tc.expectedStatusCode) // status code
77+
is.True(strings.Contains(string(body), tc.expectedBodyContains)) // body
78+
})
79+
}
80+
}
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package s3manager
2+
3+
import (
4+
"github.com/gofiber/fiber/v2"
5+
)
6+
7+
// HandleBucketsView renders all buckets on an HTML page.
8+
func (s *S3Manager) HandleBucketsView(c *fiber.Ctx) error {
9+
return c.Render("buckets", fiber.Map{})
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package s3manager_test
2+
3+
import (
4+
"io"
5+
"net/http"
6+
"strings"
7+
"testing"
8+
9+
"github.com/cloudlena/s3manager/internal/s3manager"
10+
"github.com/gofiber/fiber/v2"
11+
"github.com/gofiber/template/html/v2"
12+
"github.com/matryer/is"
13+
)
14+
15+
func TestHandleBucketsView(t *testing.T) {
16+
t.Parallel()
17+
18+
cases := []struct {
19+
it string
20+
expectedStatusCode int
21+
expectedBodyContains string
22+
}{
23+
{
24+
it: "renders the buckets page",
25+
expectedStatusCode: http.StatusOK,
26+
expectedBodyContains: "S3 Manager",
27+
},
28+
}
29+
30+
for _, tc := range cases {
31+
tc := tc
32+
t.Run(tc.it, func(t *testing.T) {
33+
t.Parallel()
34+
is := is.New(t)
35+
36+
server := s3manager.New(nil, true, "", "")
37+
38+
engine := html.New("../../views", ".html.gotmpl")
39+
app := fiber.New(fiber.Config{
40+
Views: engine,
41+
})
42+
app.Get("/buckets", server.HandleBucketsView)
43+
44+
req, err := http.NewRequest(fiber.MethodGet, "/buckets", nil)
45+
is.NoErr(err)
46+
47+
resp, err := app.Test(req)
48+
is.NoErr(err)
49+
defer resp.Body.Close()
50+
body, err := io.ReadAll(resp.Body)
51+
is.NoErr(err)
52+
53+
is.Equal(resp.StatusCode, tc.expectedStatusCode) // status code
54+
is.True(strings.Contains(string(body), tc.expectedBodyContains)) // body
55+
})
56+
}
57+
}
+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package s3manager
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/gofiber/fiber/v2"
8+
"github.com/minio/minio-go/v7"
9+
)
10+
11+
// HandleCreateBucket creates a new bucket.
12+
func (s *S3Manager) HandleCreateBucket(c *fiber.Ctx) error {
13+
name := c.FormValue("name")
14+
if strings.TrimSpace(name) == "" {
15+
return fiber.NewError(fiber.StatusBadRequest, "name is required")
16+
}
17+
18+
err := s.s3.MakeBucket(c.Context(), name, minio.MakeBucketOptions{})
19+
if err != nil {
20+
return fmt.Errorf("error making bucket: %w", err)
21+
}
22+
23+
c.Response().Header.Set("HX-Trigger", "bucketListChanged")
24+
return c.SendStatus(fiber.StatusCreated)
25+
}

0 commit comments

Comments
 (0)