diff --git a/.env.example b/.env.example index b07b4af..63b802d 100644 --- a/.env.example +++ b/.env.example @@ -1,11 +1,12 @@ -DATABASE_URL= -DEBUG= -LOG_LEVEL= -ORIGINS= -PORT= -SENTRY_DSN= -ADMIN= -ADMIN_SECRET= -API_KEY= -SECRET_KEY= -SESSION_SECRET= +APP_DATABASE_URL= +APP_DEBUG= +APP_LOG_LEVEL= +APP_ORIGINS= +APP_PORT= +APP_SENTRY_DSN= +APP_ADMIN= +APP_ADMIN_SECRET= +APP_API_KEY= +APP_SECRET_KEY= +APP_SESSION_SECRET= +APP_OPENAI_MODEL=gpt-5-nano diff --git a/AGENTS.md b/AGENTS.md index 3e721f5..c51dc36 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,52 +1,43 @@ # Repository Guidelines ## Project Structure & Module Organization - -- `main.go`: Application entrypoint (HTTP server on `:8000`). -- `internal/`: Private app code (e.g., `server/`, `service/`, `repository/`, `driver/`, - `config/`, `view/`). Templ views live in `internal/view` and generate `*_templ.go`. -- `pkg/`: Reusable packages shared across app layers. -- `cmd/`: Optional binaries/entrypoints. -- `assets/`: Static assets and images. -- `docs/`: Project documentation. -- `scripts/`, `tmp/`: Dev tooling and build artifacts (Air uses `tmp/`). +- `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`: Start dev container with live reload (Air) on `http://localhost:8000`. -- `make run`: Build and run the Docker image with `PORT` and `DATABASE_URL`. -- `make test`: Run `go test ./...` inside the running dev container. -- `make format`: Run `templ generate`, `templ fmt`, and `go fmt`. -- `make lint`: Run `golangci-lint`. -- Local alternative: `air -c .air.toml`, `go test ./...`, `go run .`. +- `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 formatting: Use `go fmt` (tabs, standard import ordering). CI helpers: - `make format`. -- Linting: `golangci-lint run ./...` via `make lint` (fix issues before PR). -- Packages: lowercase, no underscores; files: lowercase with underscores; exported - identifiers: `PascalCase`; unexported: `camelCase`. -- Templ: keep `.templ` in `internal/view`; commit sources, not generated `*_templ.go`. +- 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 - -- Framework: standard `testing` with table-driven tests. -- Files: `*_test.go`; functions: `TestXxx`, benchmarks: `BenchmarkXxx`. -- Run: `make test` (in container) or `go test ./...` locally. -- Aim for meaningful coverage on new/changed code; include error paths. +- 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 - -- Messages: Prefer Conventional Commits (e.g., `feat(parser): ...`, - `fix(config): ...`). Short, imperative first line; scope optional. -- PRs: Provide clear description, link issues (e.g., `#45`), include screenshots - for UI, and note breaking changes/migrations. -- Keep diffs focused; run `make format` and `make lint` before opening. +- 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 - -- Environment: Use `.env`/`prod.env`; never commit secrets. - Example: `DATABASE_URL="host=... user=... database=..."`. -- Ports: default `8000`; configure via `PORT`. -- Dependencies: use `go mod tidy` and `make up-deps` when updating. +- 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. diff --git a/Dockerfile b/Dockerfile index 0a8a4ca..aa824cc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.25-alpine AS builder +FROM golang:1.26-alpine AS builder RUN apk update && apk add --no-cache \ build-base \ diff --git a/Dockerfile.dev b/Dockerfile.dev index e73489c..88aefe2 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,5 +1,5 @@ # ----------- Builder Stage ----------- -FROM golang:1.25-alpine AS builder +FROM golang:1.26-alpine AS builder WORKDIR /app RUN apk add --no-cache build-base @@ -22,7 +22,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build \ go build -ldflags="-s -w" -o rentease main.go # ----------- Dev Stage ----------- -FROM golang:1.25-alpine AS dev +FROM golang:1.26-alpine AS dev WORKDIR /app # Install runtime dependencies diff --git a/README.md b/README.md index eee01d5..da2eef5 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ APP_ORIGINS=http://localhost:8000 APP_PORT=8000 APP_DEBUG=true APP_LOG_LEVEL=debug +APP_OPENAI_MODEL=gpt-5-nano APP_STRIPE_SECRET_KEY= APP_STRIPE_WEBHOOK_SECRET= APP_SENTRY_DSN= diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index e87fc4a..b807da4 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -15,6 +15,7 @@ services: APP_LOG_LEVEL: debug APP_PORT: 8000 APP_ORIGINS: http://localhost:8000 + APP_OPENAI_MODEL: gpt-5-nano APP_DATABASE_URL: postgres://rentease:rentease@db:5432/rentease?sslmode=disable APP_ADMIN: admin@example.com APP_ADMIN_SECRET: supersecret diff --git a/go.mod b/go.mod index 58760cb..4025fa7 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/rjNemo/rentease -go 1.25.4 +go 1.26 require ( github.com/a-h/templ v0.3.977 - github.com/getsentry/sentry-go v0.40.0 - github.com/go-chi/chi/v5 v5.2.3 + github.com/getsentry/sentry-go v0.42.0 + github.com/go-chi/chi/v5 v5.2.5 github.com/go-chi/cors v1.2.2 github.com/gorilla/sessions v1.4.0 github.com/joho/godotenv v1.5.1 @@ -36,6 +36,6 @@ require ( github.com/openai/openai-go v1.12.0 github.com/sethvargo/go-envconfig v1.3.0 golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.40.0 // indirect - golang.org/x/text v0.32.0 + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 ) diff --git a/go.sum b/go.sum index 4ccbf9a..551cca4 100644 --- a/go.sum +++ b/go.sum @@ -5,10 +5,10 @@ github.com/a-h/templ v0.3.977/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/getsentry/sentry-go v0.40.0 h1:VTJMN9zbTvqDqPwheRVLcp0qcUcM+8eFivvGocAaSbo= -github.com/getsentry/sentry-go v0.40.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s= -github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= -github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/getsentry/sentry-go v0.42.0 h1:eeFMACuZTbUQf90RE8dE4tXeSe4CZyfvR1MBL7RLEt8= +github.com/getsentry/sentry-go v0.42.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s= +github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= +github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE= github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= @@ -75,10 +75,10 @@ golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= -golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/config/config.go b/internal/config/config.go index 057db61..c149884 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -25,6 +25,8 @@ type Config struct { Port int `env:"PORT, default=4200"` // SentryDsn is the DSN for Sentry error reporting SentryDsn string `env:"SENTRY_DSN"` + // OpenAIModel is the OpenAI model used by the booking parser + OpenAIModel string `env:"OPENAI_MODEL, default=gpt-5-nano"` // Auth // Admin is the email used to access the admin panel Admin string `env:"ADMIN, required"` diff --git a/internal/driver/parser/client.go b/internal/driver/parser/client.go index e398f7b..5cf9774 100644 --- a/internal/driver/parser/client.go +++ b/internal/driver/parser/client.go @@ -16,11 +16,13 @@ import ( type BookingAgentParser struct { systemPrompt string llmClient openai.Client + model string } -func NewBookingAgentParser() *BookingAgentParser { +func NewBookingAgentParser(model string) *BookingAgentParser { return &BookingAgentParser{ llmClient: openai.NewClient(), + model: model, systemPrompt: ` Extract the following fields from the booking content and return a JSON object with this exact structure (all fields required, use null or empty string if not available): { @@ -71,7 +73,7 @@ func (p *BookingAgentParser) Parse(rawContent string) (*booking.Booking, error) resp, err := p.llmClient.Responses.New(ctx, responses.ResponseNewParams{ Input: responses.ResponseNewParamsInputUnion{OfString: openai.String(prompt)}, - Model: openai.ChatModelChatgpt4oLatest, + Model: p.model, }) if err != nil { return nil, fmt.Errorf("error sending request to OpenAI: %w", err) diff --git a/internal/driver/parser/parser_test.go b/internal/driver/parser/parser_test.go index 88036b7..812ef36 100644 --- a/internal/driver/parser/parser_test.go +++ b/internal/driver/parser/parser_test.go @@ -47,7 +47,7 @@ func TestBookingAgentParser_Parse(t *testing.T) { Les conversations avec vos clients apparaîtront ici. Booking.com reçoit tous les messages écrits ici et les traite selon sa Charte de confidentialité et informations sur les cookies Conditions ` - parser := NewBookingAgentParser() + parser := NewBookingAgentParser("gpt-5-nano") booking, err := parser.Parse(input) if err != nil { t.Fatalf("Parse failed: %v", err) diff --git a/internal/driver/stripe/client.go b/internal/driver/stripe/client.go index 69643a0..825eb21 100644 --- a/internal/driver/stripe/client.go +++ b/internal/driver/stripe/client.go @@ -165,7 +165,7 @@ func buildPaymentLinkCreateParams(params CreatePaymentLinkParams) (*stripe.Payme ProductData: &stripe.PaymentLinkCreateLineItemPriceDataProductDataParams{ Name: stripe.String(strings.TrimSpace(params.Description)), }, - UnitAmount: stripe.Int64(amountCents), + UnitAmount: new(amountCents), }, Quantity: stripe.Int64(1), }, diff --git a/main.go b/main.go index b1789c1..90cdb80 100644 --- a/main.go +++ b/main.go @@ -71,7 +71,7 @@ func run(ctx context.Context, appConfig *config.Config, appLogger *slog.Logger) return fmt.Errorf("error starting pdf client %w", err) } - parsingClient := parser.NewBookingAgentParser() + parsingClient := parser.NewBookingAgentParser(appConfig.OpenAIModel) var stripeClient payment.StripeClient if appConfig.StripeSecretKey != "" {