diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..5f42ec450 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: Go CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build-test-lint: + runs-on: ubuntu-latest + + steps: + - name: ๐ฅ Checkout code + uses: actions/checkout@v3 + + - name: โ๏ธ Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: ๐ฆ Install dependencies + run: go mod download + + - name: โ Lint code + run: go fmt ./... + + - name: ๐งช Run Tests + run: go test ./... -v + + - name: ๐ Build + run: go build ./cmd diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 000000000..e7ec18751 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + + "gofr.dev/internal/handler" + "gofr.dev/internal/service" + "gofr.dev/internal/store" + "gofr.dev/pkg/gofr" +) + +func main() { + fmt.Println("๐ Starting GoFr application...") + + app := gofr.New() + + storeLayer := store.New() + serviceLayer := service.New(storeLayer) + handlerLayer := handler.New(serviceLayer) + + // โ Register routes (NO DUPLICATE "/") + app.GET("/", handlerLayer.Home) + app.POST("/upload", handlerLayer.Upload) + app.GET("/download", handlerLayer.Download) + app.POST("/delete", handlerLayer.Delete) + + fmt.Println("โ Server running on http://localhost:8090") + app.Start() +} diff --git a/go.work.sum b/go.work.sum index b634c783d..78d21e6ab 100644 --- a/go.work.sum +++ b/go.work.sum @@ -9,6 +9,7 @@ cel.dev/expr v0.19.2 h1:V354PbqIXr9IQdwy4SYA4xa0HXaWq1BUPAGzugBY5V4= cel.dev/expr v0.19.2/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI= cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.23.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= @@ -717,6 +718,7 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 h1:f2Qw/Ehhimh5uO1fayV0QIW7DShEQqhtUfhYc+cBPlw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0/go.mod h1:2bIszWvQRlJVmJLiuLhukLImRjKPcYdzzsx6darK02A= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 h1:o90wcURuxekmXrtxmYWTyNla0+ZEHhud6DI1ZTxd1vI= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0/go.mod h1:6fTWu4m3jocfUZLYF5KsZC1TUfRvEjs7lM4crme/irw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50.0 h1:5IT7xOdq17MtcdtL/vtl6mGfzhaq4m4vpollPRmlsBQ= @@ -780,8 +782,11 @@ github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1Ig github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20250326154945-ae57f3c0d45f/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= @@ -808,6 +813,7 @@ github.com/docker/docker v28.0.4+incompatible h1:JNNkBctYKurkw6FrHfKqY0nKIDf5nrb github.com/docker/docker v28.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I= github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -844,6 +850,7 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-kit/log v0.1.0 h1:DGJh0Sm43HbOeYDNnVZFl8BvcYVvjD5bqYJvp0REbwQ= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= @@ -1121,6 +1128,7 @@ go.opentelemetry.io/contrib/detectors/gcp v1.33.0 h1:FVPoXEoILwgbZUu4X7YSgsESsAm go.opentelemetry.io/contrib/detectors/gcp v1.33.0/go.mod h1:ZHrLmr4ikK2AwRj9QL+c9s2SOlgoSRyMpNVzUj2fZqI= go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao= go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= +go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= @@ -1177,6 +1185,7 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= @@ -1211,6 +1220,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1243,6 +1253,7 @@ golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1266,7 +1277,9 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1316,6 +1329,7 @@ golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1453,6 +1467,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go. google.golang.org/genproto/googleapis/api v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:W9ynFDP/shebLB1Hl/ESTOap2jHd6pmLXPNZC7SVDbA= google.golang.org/genproto/googleapis/api v0.0.0-20250227231956-55c901821b1e/go.mod h1:Xsh8gBVxGCcbV8ZeTB9wI5XPyZ5RvC6V3CTeeplHbiA= google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= +google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8= +google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250106144421-5f5ef82da422 h1:w6g+P/ZscmNlGxVVXGaPVQOLu1q19ubsTOZKwaDqm4k= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250106144421-5f5ef82da422/go.mod h1:s4mHJ3FfG8P6A3O+gZ8TVqB3ufjOl9UG3ANCMMwCHmo= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250124145028-65684f501c47 h1:zYSVZD88HgcYTPowSo35t8Gdxpz+SYJ1CM0Kd/yugGw= @@ -1467,6 +1483,7 @@ google.golang.org/genproto/googleapis/bytestream v0.0.0-20250425173222-7b384671a google.golang.org/genproto/googleapis/bytestream v0.0.0-20250428153025-10db94c68c34/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250512202823-5a2f75b736a9 h1:YI36gCL8AQMhzYN6+jH8PdV/iZ0On+Zd0rO/7lCH3k8= google.golang.org/genproto/googleapis/bytestream v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20250603155806-513f23925822/go.mod h1:h6yxum/C2qRb4txaZRLDHK8RyS0H/o2oEDeKY4onY/Y= google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= @@ -1486,6 +1503,7 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250227231956-55c901821b1e/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -1505,6 +1523,7 @@ google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe0 google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20 h1:MLBCGN1O7GzIx+cBiwfYPwtmZ41U3Mn/cotLJciaArI= google.golang.org/grpc/examples v0.0.0-20230224211313-3775f633ce20/go.mod h1:Nr5H8+MlGWr5+xX/STzdoEqJrO+YteqFbMyCsrb6mH0= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= @@ -1517,6 +1536,7 @@ google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojt google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/errgo.v2 v2.1.0 h1:0vLT13EuvQ0hNvakwLuFZ/jYrLp5F3kcWHXdRggjCE8= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/internal/handler/student_handler.go b/internal/handler/student_handler.go new file mode 100644 index 000000000..554c5a41c --- /dev/null +++ b/internal/handler/student_handler.go @@ -0,0 +1,100 @@ +package handler + +import ( + + "net/http" + "html/template" + "gofr.dev/internal/service" +) + +type Handler struct { + service service.StudentService +} + +func New(s service.StudentService) Handler { + return Handler{service: s} +} + +func (h Handler) Upload(w http.ResponseWriter, r *http.Request) { + file, header, err := r.FormFile("file") + if err != nil { + http.Error(w, "โ Failed to read file: "+err.Error(), http.StatusBadRequest) + return + } + defer file.Close() + + err = h.service.UploadFile(r.Context(), header.Filename, file) + if err != nil { + http.Error(w, "โ Upload error: "+err.Error(), http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/", http.StatusSeeOther) // โ Redirect to list +} + + + +func (h Handler) List(w http.ResponseWriter, r *http.Request) { + files, err := h.service.ListFiles(r.Context()) + if err != nil { + http.Error(w, "โ List error: "+err.Error(), http.StatusInternalServerError) + return + } + + tmpl := template.Must(template.ParseFiles("templates/upload.html")) + err = tmpl.Execute(w, files) + if err != nil { + http.Error(w, "โ Template rendering error: "+err.Error(), http.StatusInternalServerError) + return + } +} + +func (h Handler) Download(w http.ResponseWriter, r *http.Request) { + filename := r.URL.Query().Get("file") + if filename == "" { + http.Error(w, "Filename is required", http.StatusBadRequest) + return + } + + data, err := h.service.DownloadFile(r.Context(), filename) + if err != nil { + http.Error(w, "Download error: "+err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Disposition", "attachment; filename="+filename) + w.Write(data) +} + +func (h Handler) Delete(w http.ResponseWriter, r *http.Request) { + err := r.ParseForm() + if err != nil { + http.Error(w, "Parse error: "+err.Error(), http.StatusBadRequest) + return + } + + filename := r.FormValue("file") + if filename == "" { + http.Error(w, "File name required", http.StatusBadRequest) + return + } + + err = h.service.DeleteFile(r.Context(), filename) + if err != nil { + http.Error(w, "Delete error: "+err.Error(), http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/", http.StatusSeeOther) +} +func (h Handler) Home(w http.ResponseWriter, r *http.Request) { + tmpl := template.Must(template.ParseFiles("templates/upload.html")) + + files, err := h.service.ListFiles(r.Context()) + if err != nil { + http.Error(w, "โ Could not fetch file list", http.StatusInternalServerError) + return + } + + tmpl.Execute(w, files) +} \ No newline at end of file diff --git a/internal/service/student_service.go b/internal/service/student_service.go new file mode 100644 index 000000000..b781fe5b4 --- /dev/null +++ b/internal/service/student_service.go @@ -0,0 +1,38 @@ +package service + +import ( + "context" + "io" +"gofr.dev/internal/store" +) + +type StudentService interface { + UploadFile(ctx context.Context, filename string, data io.Reader) error + ListFiles(ctx context.Context) ([]string, error) + DownloadFile(ctx context.Context, filename string) ([]byte, error) + DeleteFile(ctx context.Context, filename string) error +} + +type studentService struct { + store store.StudentStore +} + +func New(s store.StudentStore) StudentService { + return &studentService{store: s} +} + +func (s *studentService) UploadFile(ctx context.Context, filename string, data io.Reader) error { + return s.store.Upload(ctx, filename, data) +} + +func (s *studentService) ListFiles(ctx context.Context) ([]string, error) { + return s.store.List(ctx) +} +func (s *studentService) DownloadFile(ctx context.Context, filename string) ([]byte, error) { + return s.store.Download(ctx, filename) +} + +func (s *studentService) DeleteFile(ctx context.Context, filename string) error { + return s.store.Delete(ctx, filename) +} + diff --git a/internal/store/azure_store.go b/internal/store/azure_store.go new file mode 100644 index 000000000..ef41b0f37 --- /dev/null +++ b/internal/store/azure_store.go @@ -0,0 +1,60 @@ +package store + +import ( + "context" + "io" + "os" + + "gofr.dev/pkg/datasource/file/azureblob" +) + +type StudentStore interface { + Upload(ctx context.Context, name string, file io.Reader) error + List(ctx context.Context) ([]string, error) + Download(ctx context.Context, filename string) ([]byte, error) + Delete(ctx context.Context, filename string) error +} + + +type studentStore struct { + client *azureblob.Client +} + +func New() StudentStore { + client, _ := azureblob.NewClient( + os.Getenv("AZURE_ACCOUNT_NAME"), + os.Getenv("AZURE_ACCOUNT_KEY"), + os.Getenv("AZURE_CONTAINER_NAME"), + ) + + return &studentStore{client: client} +} + +func (s *studentStore) Upload(ctx context.Context, name string, file io.Reader) error { + return s.client.Upload(ctx, name, file) +} + +func (s *studentStore) List(ctx context.Context) ([]string, error) { + return s.client.List(ctx) +} +func (s *studentStore) Download(ctx context.Context, filename string) ([]byte, error) { + blobClient := s.client.GetContainerClient().NewBlobClient(filename) + + downloadResp, err := blobClient.DownloadStream(ctx, nil) + if err != nil { + return nil, err + } + + data, err := io.ReadAll(downloadResp.Body) + if err != nil { + return nil, err + } + + return data, nil +} + +func (s *studentStore) Delete(ctx context.Context, filename string) error { + blobClient := s.client.GetContainerClient().NewBlobClient(filename) + _, err := blobClient.Delete(ctx, nil) + return err +} diff --git a/internal/store/azure_test.go b/internal/store/azure_test.go new file mode 100644 index 000000000..49068071c --- /dev/null +++ b/internal/store/azure_test.go @@ -0,0 +1,9 @@ +package store + +import "testing" + +func TestDummy(t *testing.T) { + if 1 != 1 { + t.Error("This should never fail") + } +} diff --git a/main.go b/main.go new file mode 100644 index 000000000..983a0ff1b --- /dev/null +++ b/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + "strings" + + "gofr.dev/pkg/datasource/file/azureblob" +) + +func main() { + accountName := os.Getenv("AZURE_ACCOUNT_NAME") + accountKey := os.Getenv("AZURE_ACCOUNT_KEY") + container := os.Getenv("AZURE_CONTAINER_NAME") + + if strings.TrimSpace(accountName) == "" || strings.TrimSpace(accountKey) == "" || strings.TrimSpace(container) == "" { + log.Fatal("โ Please set AZURE_ACCOUNT_NAME, AZURE_ACCOUNT_KEY, and AZURE_CONTAINER_NAME environment variables.") + } + + client, err := azureblob.NewClient(accountName, accountKey, container) + if err != nil { + log.Fatalf("โ Failed to create Azure Blob client: %v", err) + } + + // Upload a sample file + file, err := os.Open("sample.txt") + if err != nil { + log.Fatalf("โ Failed to open sample.txt: %v", err) + } + defer file.Close() + + err = client.Upload(context.Background(), "sample.txt", file) + if err != nil { + log.Fatalf("โ Upload failed: %v", err) + } + fmt.Println("โ File uploaded successfully!") + + // List blobs + files, err := client.List(context.Background()) + if err != nil { + log.Fatalf("โ Listing failed: %v", err) + } + + fmt.Println("๐ฆ Files in Azure Blob container:") + for _, name := range files { + fmt.Println(" -", name) + } +} diff --git a/pkg/gofr/datasource/file/azureblob/client.go b/pkg/gofr/datasource/file/azureblob/client.go new file mode 100644 index 000000000..d4b93ed45 --- /dev/null +++ b/pkg/gofr/datasource/file/azureblob/client.go @@ -0,0 +1,79 @@ +package azureblob + +import ( + "context" + "fmt" + "io" + + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service" +) + +// Client handles interaction with Azure Blob Storage. +type Client struct { + containerClient *container.Client +} + +// NewClient creates a new Azure Blob client using account credentials and container name. +func NewClient(accountName, accountKey, containerName string) (*Client, error) { + cred, err := azblob.NewSharedKeyCredential(accountName, accountKey) + if err != nil { + return nil, fmt.Errorf("invalid credentials: %w", err) + } + + serviceURL := fmt.Sprintf("https://%s.blob.core.windows.net/", accountName) + svcClient, err := service.NewClientWithSharedKeyCredential(serviceURL, cred, nil) + if err != nil { + return nil, fmt.Errorf("failed to create service client: %w", err) + } + + containerClient := svcClient.NewContainerClient(containerName) + return &Client{containerClient: containerClient}, nil +} + +// GetContainerClient returns the container client. +func (c *Client) GetContainerClient() *container.Client { + return c.containerClient +} + +// Upload uploads a blob to the container. +func (c *Client) Upload(ctx context.Context, blobName string, data io.Reader) error { + blobClient := c.containerClient.NewBlockBlobClient(blobName) + _, err := blobClient.UploadStream(ctx, data, nil) + return err +} + +// Download downloads a blob and returns a reader. +func (c *Client) Download(ctx context.Context, blobName string) (io.ReadCloser, error) { + blobClient := c.containerClient.NewBlobClient(blobName) + resp, err := blobClient.DownloadStream(ctx, nil) + if err != nil { + return nil, err + } + return resp.Body, nil +} + +// Delete deletes a blob from the container. +func (c *Client) Delete(ctx context.Context, blobName string) error { + blobClient := c.containerClient.NewBlobClient(blobName) + _, err := blobClient.Delete(ctx, nil) + return err +} + +// List returns the names of all blobs in the container. +func (c *Client) List(ctx context.Context) ([]string, error) { + pager := c.containerClient.NewListBlobsFlatPager(nil) + var names []string + + for pager.More() { + page, err := pager.NextPage(ctx) + if err != nil { + return nil, err + } + for _, blob := range page.Segment.BlobItems { + names = append(names, *blob.Name) + } + } + return names, nil +} diff --git a/pkg/gofr/datasource/file/azureblob/client_test.go b/pkg/gofr/datasource/file/azureblob/client_test.go new file mode 100644 index 000000000..2e6f4aee3 --- /dev/null +++ b/pkg/gofr/datasource/file/azureblob/client_test.go @@ -0,0 +1,7 @@ +package azureblob + +import "testing" + +func TestPlaceholder(t *testing.T) { + t.Log("Write mock-based tests here.") +} diff --git a/templates/upload.html b/templates/upload.html new file mode 100644 index 000000000..1e8e5fabe --- /dev/null +++ b/templates/upload.html @@ -0,0 +1,93 @@ + + +
+No files uploaded yet.
+ {{end}} +