fix: normalize minio endpoints
Some checks failed
CI / checks (push) Has been cancelled

This commit is contained in:
Ruidy 2026-03-21 10:19:33 +01:00
parent 9b2510460a
commit 0f327c814a
No known key found for this signature in database
GPG key ID: 705C24D202990805
17 changed files with 81 additions and 63 deletions

View file

@ -1,43 +0,0 @@
# Repository Guidelines
## Project Structure & Module Organization
- `main.go`: application entrypoint (HTTP server).
- `internal/`: core app code by layer:
- `server/` (HTTP handlers/routes), `service/` (business logic), `repository/` (data access), `driver/` (external integrations), `config/` (env config), `view/` (templ views/viewmodels).
- `internal/view/*.templ` are source templates; generated files are `internal/view/*_templ.go`.
- `cmd/cron/`: cron entrypoint.
- `assets/`: static assets. `docs/`: documentation/images. `scripts/` and `tmp/`: tooling/artifacts.
- Tests live next to code as `*_test.go`.
## Build, Test, and Development Commands
- `make dev`: run local dev stack with Docker Compose and hot reload.
- `make run`: build and run production image locally.
- `make test`: run `go test ./...`.
- `make format`: run `templ generate`, `templ fmt`, and `go fmt`.
- `make lint`: run `golangci-lint run ./...`.
- `make stop`: stop dev containers.
- Local (without Docker): `go run .`, `go test ./...`.
## Coding Style & Naming Conventions
- Go style is standard `go fmt` (tabs, canonical imports).
- Package names: lowercase, no underscores.
- File names: lowercase with underscores when needed.
- Exported identifiers: `PascalCase`; unexported: `camelCase`.
- Keep handler/controller code thin; place business rules in `internal/service`.
## Testing Guidelines
- Use Go `testing` with table-driven tests where practical.
- Name files `*_test.go`; tests `TestXxx`; benchmarks `BenchmarkXxx`.
- Cover success and failure paths for changed logic.
- Run `make test` (or `go test ./...`) before opening a PR.
## Commit & Pull Request Guidelines
- Prefer Conventional Commits: `feat(scope): ...`, `fix(scope): ...`, `chore: ...`.
- Keep commits focused and atomic.
- PRs should include: clear summary, linked issue (e.g., `#51`), and screenshots for UI changes.
- Call out config/env changes and any migration or deployment impact.
## Security & Configuration Tips
- Config is environment-driven with `APP_` prefix (see `internal/config/config.go`).
- Example parser model override: `APP_OPENAI_MODEL=gpt-5-nano`.
- Never commit secrets; keep them in local `.env` / deployment secret manager.

View file

@ -4,6 +4,8 @@ import (
"bytes"
"context"
"fmt"
"net/url"
"strings"
"time"
"github.com/minio/minio-go/v7"
@ -34,9 +36,14 @@ func NewMinioInvoiceStorage(cfg *config.Config) (*MinioInvoiceStorage, error) {
return nil, fmt.Errorf("minio bucket is required")
}
client, err := minio.New(cfg.MinIOEndpoint, &minio.Options{
endpoint, secure, err := normalizeEndpoint(cfg.MinIOEndpoint, cfg.MinIOUseSSL)
if err != nil {
return nil, fmt.Errorf("normalize minio endpoint: %w", err)
}
client, err := minio.New(endpoint, &minio.Options{
Creds: credentials.NewStaticV4(cfg.MinIOAccessKey, cfg.MinIOSecretKey, ""),
Secure: cfg.MinIOUseSSL,
Secure: secure,
})
if err != nil {
return nil, fmt.Errorf("create minio client: %w", err)
@ -48,6 +55,29 @@ func NewMinioInvoiceStorage(cfg *config.Config) (*MinioInvoiceStorage, error) {
}, nil
}
func normalizeEndpoint(raw string, secure bool) (string, bool, error) {
raw = strings.TrimSpace(raw)
if raw == "" {
return "", secure, fmt.Errorf("endpoint is required")
}
if strings.HasPrefix(raw, "http://") || strings.HasPrefix(raw, "https://") {
u, err := url.Parse(raw)
if err != nil {
return "", secure, fmt.Errorf("parse endpoint: %w", err)
}
if u.Host == "" {
return "", secure, fmt.Errorf("endpoint host is required")
}
if u.Path != "" && u.Path != "/" {
return "", secure, fmt.Errorf("endpoint path is not supported")
}
return u.Host, u.Scheme == "https", nil
}
return raw, secure, nil
}
func (s *MinioInvoiceStorage) StoreInvoice(
ctx context.Context,
objectKey string,

View file

@ -0,0 +1,31 @@
package storage
import "testing"
func TestNormalizeEndpoint_WithSchemeOverridesSecureFlag(t *testing.T) {
endpoint, secure, err := normalizeEndpoint("https://s3api.nemausat.com", false)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if endpoint != "s3api.nemausat.com" {
t.Fatalf("expected normalized host, got %q", endpoint)
}
if !secure {
t.Fatal("expected https endpoint to force secure=true")
}
}
func TestNormalizeEndpoint_WithoutSchemePreservesSecureFlag(t *testing.T) {
endpoint, secure, err := normalizeEndpoint("s3api.nemausat.com", true)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if endpoint != "s3api.nemausat.com" {
t.Fatalf("expected endpoint to be preserved, got %q", endpoint)
}
if !secure {
t.Fatal("expected secure flag to be preserved")
}
}

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
// templ: version: v0.3.1001
package view
//lint:file-ignore SA4006 This context is only used if a nested component is present.
@ -123,7 +123,7 @@ func BookingById(booking *BookingViewModel) templ.Component {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<button class=\"btn btn-outline\" hx-patch=\"")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<button class=\"btn btn-outline\" hx-patch=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -175,7 +175,7 @@ func BookingById(booking *BookingViewModel) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</h2><div class=\"flex items-center gap-2\"><button class=\"btn btn-secondary btn-sm \" onclick=\"document.getElementById('payment_modal').showModal()\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</h2><div class=\"flex items-center gap-2\"><button class=\"btn btn-secondary btn-sm\" onclick=\"document.getElementById('payment_modal').showModal()\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
// templ: version: v0.3.1001
package view
//lint:file-ignore SA4006 This context is only used if a nested component is present.

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
// templ: version: v0.3.1001
package view
//lint:file-ignore SA4006 This context is only used if a nested component is present.

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
// templ: version: v0.3.1001
package view
//lint:file-ignore SA4006 This context is only used if a nested component is present.

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
// templ: version: v0.3.1001
package view
//lint:file-ignore SA4006 This context is only used if a nested component is present.

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
// templ: version: v0.3.1001
package view
//lint:file-ignore SA4006 This context is only used if a nested component is present.

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
// templ: version: v0.3.1001
package layout
//lint:file-ignore SA4006 This context is only used if a nested component is present.
@ -188,7 +188,7 @@ func navbar(lang string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</a></div><div class=\"navbar-end\"><div class=\" hidden lg:flex\"><ul class=\"menu menu-horizontal px-1\"><li><a href=\"/bookings\">")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "</a></div><div class=\"navbar-end\"><div class=\"hidden lg:flex\"><ul class=\"menu menu-horizontal px-1\"><li><a href=\"/bookings\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
// templ: version: v0.3.1001
package view
//lint:file-ignore SA4006 This context is only used if a nested component is present.

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
// templ: version: v0.3.1001
package view
//lint:file-ignore SA4006 This context is only used if a nested component is present.

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
// templ: version: v0.3.1001
package view
//lint:file-ignore SA4006 This context is only used if a nested component is present.

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
// templ: version: v0.3.1001
package view
//lint:file-ignore SA4006 This context is only used if a nested component is present.

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
// templ: version: v0.3.1001
package view
//lint:file-ignore SA4006 This context is only used if a nested component is present.

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
// templ: version: v0.3.1001
package view
//lint:file-ignore SA4006 This context is only used if a nested component is present.
@ -211,7 +211,7 @@ func Reports(months []string, m int, year string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</button></div></form></section><section id=\"report\" class=\" px-4 mt-8\"></section>")
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</button></div></form></section><section id=\"report\" class=\"px-4 mt-8\"></section>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View file

@ -1,6 +1,6 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
// templ: version: v0.3.1001
package view
//lint:file-ignore SA4006 This context is only used if a nested component is present.