chore: wire APP_OPENAI_MODEL and upgrade Go/deps

This commit is contained in:
Ruidy 2026-02-18 18:25:34 +01:00
parent 584d81f7bd
commit df1cd66cf1
No known key found for this signature in database
GPG key ID: 705C24D202990805
13 changed files with 69 additions and 71 deletions

View file

@ -1,11 +1,12 @@
DATABASE_URL= APP_DATABASE_URL=
DEBUG= APP_DEBUG=
LOG_LEVEL= APP_LOG_LEVEL=
ORIGINS= APP_ORIGINS=
PORT= APP_PORT=
SENTRY_DSN= APP_SENTRY_DSN=
ADMIN= APP_ADMIN=
ADMIN_SECRET= APP_ADMIN_SECRET=
API_KEY= APP_API_KEY=
SECRET_KEY= APP_SECRET_KEY=
SESSION_SECRET= APP_SESSION_SECRET=
APP_OPENAI_MODEL=gpt-5-nano

View file

@ -1,52 +1,43 @@
# Repository Guidelines # Repository Guidelines
## Project Structure & Module Organization ## Project Structure & Module Organization
- `main.go`: application entrypoint (HTTP server).
- `main.go`: Application entrypoint (HTTP server on `:8000`). - `internal/`: core app code by layer:
- `internal/`: Private app code (e.g., `server/`, `service/`, `repository/`, `driver/`, - `server/` (HTTP handlers/routes), `service/` (business logic), `repository/` (data access), `driver/` (external integrations), `config/` (env config), `view/` (templ views/viewmodels).
`config/`, `view/`). Templ views live in `internal/view` and generate `*_templ.go`. - `internal/view/*.templ` are source templates; generated files are `internal/view/*_templ.go`.
- `pkg/`: Reusable packages shared across app layers. - `cmd/cron/`: cron entrypoint.
- `cmd/`: Optional binaries/entrypoints. - `assets/`: static assets. `docs/`: documentation/images. `scripts/` and `tmp/`: tooling/artifacts.
- `assets/`: Static assets and images. - Tests live next to code as `*_test.go`.
- `docs/`: Project documentation.
- `scripts/`, `tmp/`: Dev tooling and build artifacts (Air uses `tmp/`).
## Build, Test, and Development Commands ## Build, Test, and Development Commands
- `make dev`: run local dev stack with Docker Compose and hot reload.
- `make dev`: Start dev container with live reload (Air) on `http://localhost:8000`. - `make run`: build and run production image locally.
- `make run`: Build and run the Docker image with `PORT` and `DATABASE_URL`. - `make test`: run `go test ./...`.
- `make test`: Run `go test ./...` inside the running dev container. - `make format`: run `templ generate`, `templ fmt`, and `go fmt`.
- `make format`: Run `templ generate`, `templ fmt`, and `go fmt`. - `make lint`: run `golangci-lint run ./...`.
- `make lint`: Run `golangci-lint`. - `make stop`: stop dev containers.
- Local alternative: `air -c .air.toml`, `go test ./...`, `go run .`. - Local (without Docker): `go run .`, `go test ./...`.
## Coding Style & Naming Conventions ## Coding Style & Naming Conventions
- Go style is standard `go fmt` (tabs, canonical imports).
- Go formatting: Use `go fmt` (tabs, standard import ordering). CI helpers: - Package names: lowercase, no underscores.
`make format`. - File names: lowercase with underscores when needed.
- Linting: `golangci-lint run ./...` via `make lint` (fix issues before PR). - Exported identifiers: `PascalCase`; unexported: `camelCase`.
- Packages: lowercase, no underscores; files: lowercase with underscores; exported - Keep handler/controller code thin; place business rules in `internal/service`.
identifiers: `PascalCase`; unexported: `camelCase`.
- Templ: keep `.templ` in `internal/view`; commit sources, not generated `*_templ.go`.
## Testing Guidelines ## Testing Guidelines
- Use Go `testing` with table-driven tests where practical.
- Framework: standard `testing` with table-driven tests. - Name files `*_test.go`; tests `TestXxx`; benchmarks `BenchmarkXxx`.
- Files: `*_test.go`; functions: `TestXxx`, benchmarks: `BenchmarkXxx`. - Cover success and failure paths for changed logic.
- Run: `make test` (in container) or `go test ./...` locally. - Run `make test` (or `go test ./...`) before opening a PR.
- Aim for meaningful coverage on new/changed code; include error paths.
## Commit & Pull Request Guidelines ## Commit & Pull Request Guidelines
- Prefer Conventional Commits: `feat(scope): ...`, `fix(scope): ...`, `chore: ...`.
- Messages: Prefer Conventional Commits (e.g., `feat(parser): ...`, - Keep commits focused and atomic.
`fix(config): ...`). Short, imperative first line; scope optional. - PRs should include: clear summary, linked issue (e.g., `#51`), and screenshots for UI changes.
- PRs: Provide clear description, link issues (e.g., `#45`), include screenshots - Call out config/env changes and any migration or deployment impact.
for UI, and note breaking changes/migrations.
- Keep diffs focused; run `make format` and `make lint` before opening.
## Security & Configuration Tips ## Security & Configuration Tips
- Config is environment-driven with `APP_` prefix (see `internal/config/config.go`).
- Environment: Use `.env`/`prod.env`; never commit secrets. - Example parser model override: `APP_OPENAI_MODEL=gpt-5-nano`.
Example: `DATABASE_URL="host=... user=... database=..."`. - Never commit secrets; keep them in local `.env` / deployment secret manager.
- Ports: default `8000`; configure via `PORT`.
- Dependencies: use `go mod tidy` and `make up-deps` when updating.

View file

@ -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 \ RUN apk update && apk add --no-cache \
build-base \ build-base \

View file

@ -1,5 +1,5 @@
# ----------- Builder Stage ----------- # ----------- Builder Stage -----------
FROM golang:1.25-alpine AS builder FROM golang:1.26-alpine AS builder
WORKDIR /app WORKDIR /app
RUN apk add --no-cache build-base 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 go build -ldflags="-s -w" -o rentease main.go
# ----------- Dev Stage ----------- # ----------- Dev Stage -----------
FROM golang:1.25-alpine AS dev FROM golang:1.26-alpine AS dev
WORKDIR /app WORKDIR /app
# Install runtime dependencies # Install runtime dependencies

View file

@ -83,6 +83,7 @@ APP_ORIGINS=http://localhost:8000
APP_PORT=8000 APP_PORT=8000
APP_DEBUG=true APP_DEBUG=true
APP_LOG_LEVEL=debug APP_LOG_LEVEL=debug
APP_OPENAI_MODEL=gpt-5-nano
APP_STRIPE_SECRET_KEY= APP_STRIPE_SECRET_KEY=
APP_STRIPE_WEBHOOK_SECRET= APP_STRIPE_WEBHOOK_SECRET=
APP_SENTRY_DSN= APP_SENTRY_DSN=

View file

@ -15,6 +15,7 @@ services:
APP_LOG_LEVEL: debug APP_LOG_LEVEL: debug
APP_PORT: 8000 APP_PORT: 8000
APP_ORIGINS: http://localhost:8000 APP_ORIGINS: http://localhost:8000
APP_OPENAI_MODEL: gpt-5-nano
APP_DATABASE_URL: postgres://rentease:rentease@db:5432/rentease?sslmode=disable APP_DATABASE_URL: postgres://rentease:rentease@db:5432/rentease?sslmode=disable
APP_ADMIN: admin@example.com APP_ADMIN: admin@example.com
APP_ADMIN_SECRET: supersecret APP_ADMIN_SECRET: supersecret

10
go.mod
View file

@ -1,11 +1,11 @@
module github.com/rjNemo/rentease module github.com/rjNemo/rentease
go 1.25.4 go 1.26
require ( require (
github.com/a-h/templ v0.3.977 github.com/a-h/templ v0.3.977
github.com/getsentry/sentry-go v0.40.0 github.com/getsentry/sentry-go v0.42.0
github.com/go-chi/chi/v5 v5.2.3 github.com/go-chi/chi/v5 v5.2.5
github.com/go-chi/cors v1.2.2 github.com/go-chi/cors v1.2.2
github.com/gorilla/sessions v1.4.0 github.com/gorilla/sessions v1.4.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
@ -36,6 +36,6 @@ require (
github.com/openai/openai-go v1.12.0 github.com/openai/openai-go v1.12.0
github.com/sethvargo/go-envconfig v1.3.0 github.com/sethvargo/go-envconfig v1.3.0
golang.org/x/sync v0.19.0 // indirect golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.32.0 golang.org/x/text v0.34.0
) )

16
go.sum
View file

@ -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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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.42.0 h1:eeFMACuZTbUQf90RE8dE4tXeSe4CZyfvR1MBL7RLEt8=
github.com/getsentry/sentry-go v0.40.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s= github.com/getsentry/sentry-go v0.42.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.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 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 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= 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/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 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= 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/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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View file

@ -25,6 +25,8 @@ type Config struct {
Port int `env:"PORT, default=4200"` Port int `env:"PORT, default=4200"`
// SentryDsn is the DSN for Sentry error reporting // SentryDsn is the DSN for Sentry error reporting
SentryDsn string `env:"SENTRY_DSN"` 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 // Auth
// Admin is the email used to access the admin panel // Admin is the email used to access the admin panel
Admin string `env:"ADMIN, required"` Admin string `env:"ADMIN, required"`

View file

@ -16,11 +16,13 @@ import (
type BookingAgentParser struct { type BookingAgentParser struct {
systemPrompt string systemPrompt string
llmClient openai.Client llmClient openai.Client
model string
} }
func NewBookingAgentParser() *BookingAgentParser { func NewBookingAgentParser(model string) *BookingAgentParser {
return &BookingAgentParser{ return &BookingAgentParser{
llmClient: openai.NewClient(), 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): 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{ resp, err := p.llmClient.Responses.New(ctx, responses.ResponseNewParams{
Input: responses.ResponseNewParamsInputUnion{OfString: openai.String(prompt)}, Input: responses.ResponseNewParamsInputUnion{OfString: openai.String(prompt)},
Model: openai.ChatModelChatgpt4oLatest, Model: p.model,
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("error sending request to OpenAI: %w", err) return nil, fmt.Errorf("error sending request to OpenAI: %w", err)

View file

@ -47,7 +47,7 @@ func TestBookingAgentParser_Parse(t *testing.T) {
Les conversations avec vos clients apparaîtront ici. 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 ` 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) booking, err := parser.Parse(input)
if err != nil { if err != nil {
t.Fatalf("Parse failed: %v", err) t.Fatalf("Parse failed: %v", err)

View file

@ -165,7 +165,7 @@ func buildPaymentLinkCreateParams(params CreatePaymentLinkParams) (*stripe.Payme
ProductData: &stripe.PaymentLinkCreateLineItemPriceDataProductDataParams{ ProductData: &stripe.PaymentLinkCreateLineItemPriceDataProductDataParams{
Name: stripe.String(strings.TrimSpace(params.Description)), Name: stripe.String(strings.TrimSpace(params.Description)),
}, },
UnitAmount: stripe.Int64(amountCents), UnitAmount: new(amountCents),
}, },
Quantity: stripe.Int64(1), Quantity: stripe.Int64(1),
}, },

View file

@ -71,7 +71,7 @@ func run(ctx context.Context, appConfig *config.Config, appLogger *slog.Logger)
return fmt.Errorf("error starting pdf client %w", err) return fmt.Errorf("error starting pdf client %w", err)
} }
parsingClient := parser.NewBookingAgentParser() parsingClient := parser.NewBookingAgentParser(appConfig.OpenAIModel)
var stripeClient payment.StripeClient var stripeClient payment.StripeClient
if appConfig.StripeSecretKey != "" { if appConfig.StripeSecretKey != "" {