Skip to content

Commit d150c18

Browse files
committed
merge cmd logic
1 parent 7c678f5 commit d150c18

27 files changed

Lines changed: 944 additions & 614 deletions

.github/workflows/release.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ on:
88
tags:
99
# Additional packages must be added both here AND in the job list below:
1010
- "v*"
11+
- "riverproui/v*"
1112

1213
jobs:
1314
release_riverui:
@@ -25,3 +26,18 @@ jobs:
2526
r2-access-key-id: ${{ secrets.R2_ACCESS_KEY_ID }}
2627
r2-endpoint-url: ${{ secrets.R2_ENDPOINT_URL }}
2728
r2-secret-access-key: ${{ secrets.R2_SECRET_ACCESS_KEY }}
29+
release_riverproui:
30+
uses: ./.github/workflows/package-and-release.yaml
31+
if: startsWith(github.ref, 'refs/tags/riverproui/v')
32+
with:
33+
after-version: v0.4.0
34+
module-base: riverqueue.com/riverui/riverproui
35+
module-dir: ./riverproui
36+
storage-bucket: ${{ vars.RELEASE_STORAGE_BUCKET }}
37+
version-tag: ${{ github.ref_name}}
38+
permissions:
39+
contents: read
40+
secrets:
41+
r2-access-key-id: ${{ secrets.R2_ACCESS_KEY_ID }}
42+
r2-endpoint-url: ${{ secrets.R2_ENDPOINT_URL }}
43+
r2-secret-access-key: ${{ secrets.R2_SECRET_ACCESS_KEY }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
/packager/packager
55
.tool-versions
66
/riverui
7+
/riverproui/riverproui
78

89
# Logs
910
logs

.reflex.pro.server

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
-r '\.(go)$' -R 'node_modules' -G '\*\_test.go' -s -- go run -C cmd/riverproui -race .
1+
-r '\.(go)$' -R 'node_modules' -G '\*\_test.go' -s -- sh -c "export RIVER_UI_PROJECT_ROOT=$(git rev-parse --show-toplevel) && go run -C riverproui/cmd/riverproui -race ."

.reflex.server

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
-r '\.(go)$' -R 'node_modules' -G '\*\_test.go' -s -- go run -C cmd/riverui -race .
1+
-r '\.(go)$' -R 'node_modules' -G '\*\_test.go' -s -- sh -c "export RIVER_UI_PROJECT_ROOT=$(git rev-parse --show-toplevel) && go run -C cmd/riverui -race ."

cmd/riverproui/go.mod

Lines changed: 0 additions & 75 deletions
This file was deleted.

cmd/riverproui/pro_handler_bundle.go

Lines changed: 0 additions & 1 deletion
This file was deleted.

cmd/riverui/main.go

Lines changed: 13 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -1,183 +1,26 @@
11
package main
22

33
import (
4-
"cmp"
5-
"context"
6-
"errors"
7-
"flag"
8-
"fmt"
9-
"log/slog"
10-
"net/http"
11-
"os"
12-
"strings"
13-
"time"
14-
4+
"github.com/jackc/pgx/v5"
155
"github.com/jackc/pgx/v5/pgxpool"
16-
"github.com/rs/cors"
17-
sloghttp "github.com/samber/slog-http"
186
"riverqueue.com/riverui"
19-
"riverqueue.com/riverui/authmiddleware"
7+
"riverqueue.com/riverui/internal/apibundle"
8+
"riverqueue.com/riverui/internal/riveruicmd"
209

21-
"github.com/riverqueue/apiframe/apimiddleware"
2210
"github.com/riverqueue/river"
2311
"github.com/riverqueue/river/riverdriver/riverpgxv5"
2412
)
2513

2614
func main() {
27-
ctx := context.Background()
28-
29-
logger := slog.New(getLogHandler(&slog.HandlerOptions{
30-
Level: getLogLevel(),
31-
}))
32-
33-
var pathPrefix string
34-
flag.StringVar(&pathPrefix, "prefix", "/", "path prefix to use for the API and UI HTTP requests")
35-
flag.Parse()
36-
37-
initRes, err := initServer(ctx, logger, pathPrefix)
38-
if err != nil {
39-
logger.ErrorContext(ctx, "Error initializing server", slog.String("error", err.Error()))
40-
os.Exit(1)
41-
}
42-
43-
if err := startAndListen(ctx, logger, initRes); err != nil {
44-
logger.ErrorContext(ctx, "Error starting server", slog.String("error", err.Error()))
45-
os.Exit(1)
46-
}
47-
}
48-
49-
// Translates either a "1" or "true" from env to a Go boolean.
50-
func envBooleanTrue(val string) bool {
51-
return val == "1" || val == "true"
52-
}
53-
54-
func getLogHandler(opts *slog.HandlerOptions) slog.Handler {
55-
logFormat := strings.ToLower(os.Getenv("RIVER_LOG_FORMAT"))
56-
switch logFormat {
57-
case "json":
58-
return slog.NewJSONHandler(os.Stdout, opts)
59-
default:
60-
return slog.NewTextHandler(os.Stdout, opts)
61-
}
62-
}
63-
64-
func getLogLevel() slog.Level {
65-
if envBooleanTrue(os.Getenv("RIVER_DEBUG")) {
66-
return slog.LevelDebug
67-
}
68-
69-
switch strings.ToLower(os.Getenv("RIVER_LOG_LEVEL")) {
70-
case "debug":
71-
return slog.LevelDebug
72-
case "warn":
73-
return slog.LevelWarn
74-
case "error":
75-
return slog.LevelError
76-
default:
77-
return slog.LevelInfo
78-
}
79-
}
80-
81-
type initServerResult struct {
82-
dbPool *pgxpool.Pool // database pool; close must be deferred by caller!
83-
httpServer *http.Server // HTTP server wrapping the UI server
84-
logger *slog.Logger // application logger (also internalized in UI server)
85-
uiServer *riverui.Server // River UI server
86-
}
87-
88-
func initServer(ctx context.Context, logger *slog.Logger, pathPrefix string) (*initServerResult, error) {
89-
if !strings.HasPrefix(pathPrefix, "/") || pathPrefix == "" {
90-
return nil, fmt.Errorf("invalid path prefix: %s", pathPrefix)
91-
}
92-
pathPrefix = riverui.NormalizePathPrefix(pathPrefix)
93-
94-
var (
95-
basicAuthUsername = os.Getenv("RIVER_BASIC_AUTH_USER")
96-
basicAuthPassword = os.Getenv("RIVER_BASIC_AUTH_PASS")
97-
corsOrigins = strings.Split(os.Getenv("CORS_ORIGINS"), ",")
98-
databaseURL = os.Getenv("DATABASE_URL")
99-
devMode = envBooleanTrue(os.Getenv("DEV"))
100-
jobListHideArgsByDefault = envBooleanTrue(os.Getenv("RIVER_JOB_LIST_HIDE_ARGS_BY_DEFAULT"))
101-
host = os.Getenv("RIVER_HOST") // may be left empty to bind to all local interfaces
102-
liveFS = envBooleanTrue(os.Getenv("LIVE_FS"))
103-
otelEnabled = envBooleanTrue(os.Getenv("OTEL_ENABLED"))
104-
port = cmp.Or(os.Getenv("PORT"), "8080")
105-
)
106-
107-
if databaseURL == "" && os.Getenv("PGDATABASE") == "" {
108-
return nil, errors.New("expect to have DATABASE_URL or database configuration in standard PG* env vars like PGDATABASE/PGHOST/PGPORT/PGUSER/PGPASSWORD")
109-
}
110-
111-
poolConfig, err := pgxpool.ParseConfig(databaseURL)
112-
if err != nil {
113-
return nil, fmt.Errorf("error parsing db config: %w", err)
114-
}
115-
116-
dbPool, err := pgxpool.NewWithConfig(ctx, poolConfig)
117-
if err != nil {
118-
return nil, fmt.Errorf("error connecting to db: %w", err)
119-
}
120-
121-
client, err := river.NewClient(riverpgxv5.New(dbPool), &river.Config{})
122-
if err != nil {
123-
return nil, err
124-
}
125-
126-
uiServer, err := riverui.NewServer(&riverui.ServerOpts{
127-
Client: client,
128-
DB: dbPool,
129-
DevMode: devMode,
130-
JobListHideArgsByDefault: jobListHideArgsByDefault,
131-
LiveFS: liveFS,
132-
Logger: logger,
133-
Prefix: pathPrefix,
134-
})
135-
if err != nil {
136-
return nil, err
137-
}
138-
139-
corsHandler := cors.New(cors.Options{
140-
AllowedMethods: []string{"GET", "HEAD", "POST", "PUT"},
141-
AllowedOrigins: corsOrigins,
142-
})
143-
logHandler := sloghttp.NewWithConfig(logger, sloghttp.Config{
144-
WithSpanID: otelEnabled,
145-
WithTraceID: otelEnabled,
146-
})
147-
148-
middlewareStack := apimiddleware.NewMiddlewareStack(
149-
apimiddleware.MiddlewareFunc(sloghttp.Recovery),
150-
apimiddleware.MiddlewareFunc(corsHandler.Handler),
151-
apimiddleware.MiddlewareFunc(logHandler),
152-
)
153-
if basicAuthUsername != "" && basicAuthPassword != "" {
154-
middlewareStack.Use(&authmiddleware.BasicAuth{Username: basicAuthUsername, Password: basicAuthPassword})
155-
}
156-
157-
return &initServerResult{
158-
dbPool: dbPool,
159-
httpServer: &http.Server{
160-
Addr: host + ":" + port,
161-
Handler: middlewareStack.Mount(uiServer),
162-
ReadHeaderTimeout: 5 * time.Second,
15+
riveruicmd.Run(
16+
func(dbPool *pgxpool.Pool) (*river.Client[pgx.Tx], error) {
17+
return river.NewClient(riverpgxv5.New(dbPool), &river.Config{})
16318
},
164-
logger: logger,
165-
uiServer: uiServer,
166-
}, nil
167-
}
168-
169-
func startAndListen(ctx context.Context, logger *slog.Logger, initRes *initServerResult) error {
170-
defer initRes.dbPool.Close()
171-
172-
if err := initRes.uiServer.Start(ctx); err != nil {
173-
return err
174-
}
175-
176-
logger.InfoContext(ctx, "Starting server", slog.String("addr", initRes.httpServer.Addr))
177-
178-
if err := initRes.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
179-
return err
180-
}
181-
182-
return nil
19+
func(client *river.Client[pgx.Tx], opts *riveruicmd.BundleOpts) apibundle.EndpointBundle {
20+
return riverui.NewEndpoints(&riverui.EndpointsOpts[pgx.Tx]{
21+
Client: client,
22+
JobListHideArgsByDefault: opts.JobListHideArgsByDefault,
23+
})
24+
},
25+
)
18326
}

example_test.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"os"
1111
"strings"
1212

13+
"github.com/jackc/pgx/v5"
1314
"github.com/jackc/pgx/v5/pgxpool"
1415
"riverqueue.com/riverui"
1516

@@ -51,10 +52,10 @@ func ExampleNewServer() {
5152

5253
// Create the River UI server. This server implements http.Handler and can be
5354
// mounted in an HTTP mux
54-
server, err := riverui.NewServer(&riverui.ServerOpts{
55-
Client: client,
55+
server, err := riverui.NewServer(riverui.NewEndpoints(&riverui.EndpointsOpts[pgx.Tx]{
56+
Client: client,
57+
}), &riverui.ServerOpts{
5658
DevMode: true, // Use the live filesystem—don't use this outside tests
57-
DB: dbPool,
5859
Logger: logger,
5960
Prefix: "/riverui", // Mount the UI under /riverui path
6061
})

go.mod

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ require (
88
github.com/google/uuid v1.6.0
99
github.com/jackc/pgx/v5 v5.7.5
1010
github.com/riverqueue/apiframe v0.0.0-20250408034821-b206bbbd0fb4
11-
github.com/riverqueue/river v0.23.1
12-
github.com/riverqueue/river/riverdriver v0.23.1
13-
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.23.1
14-
github.com/riverqueue/river/rivershared v0.23.1
15-
github.com/riverqueue/river/rivertype v0.23.1
11+
github.com/riverqueue/river v0.23.2-0.20250724035158-e771d8dbe258
12+
github.com/riverqueue/river/riverdriver v0.23.2-0.20250724035158-e771d8dbe258
13+
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.23.2-0.20250724035158-e771d8dbe258
14+
github.com/riverqueue/river/rivershared v0.23.2-0.20250724035158-e771d8dbe258
15+
github.com/riverqueue/river/rivertype v0.23.2-0.20250724035158-e771d8dbe258
1616
github.com/rs/cors v1.11.1
1717
github.com/samber/slog-http v1.7.0
1818
github.com/stretchr/testify v1.10.0
@@ -44,15 +44,3 @@ require (
4444
golang.org/x/text v0.27.0 // indirect
4545
gopkg.in/yaml.v3 v3.0.1 // indirect
4646
)
47-
48-
replace github.com/riverqueue/river => ../river
49-
50-
replace github.com/riverqueue/river/riverdriver => ../river/riverdriver
51-
52-
replace github.com/riverqueue/river/riverpilot => ../river/riverpilot
53-
54-
replace github.com/riverqueue/river/rivershared => ../river/rivershared
55-
56-
replace github.com/riverqueue/river/rivertype => ../river/rivertype
57-
58-
replace github.com/riverqueue/river/riverdriver/riverpgxv5 => ../river/riverdriver/riverpgxv5

go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
3535
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
3636
github.com/riverqueue/apiframe v0.0.0-20250408034821-b206bbbd0fb4 h1:ejJogJ57bF+jMbvGjZQ6H6LR0NCTDQr30SJ/wSVepgs=
3737
github.com/riverqueue/apiframe v0.0.0-20250408034821-b206bbbd0fb4/go.mod h1:6aXA9FSXKkxwjbOUSXdrIOuw478Lvtz/eEu45R4MoQk=
38+
github.com/riverqueue/river v0.23.2-0.20250724035158-e771d8dbe258 h1:mmknm9D3zlx4MWkd5gpwm2NnKvNOdhohN0G+UZow+7Y=
39+
github.com/riverqueue/river v0.23.2-0.20250724035158-e771d8dbe258/go.mod h1:6WkZJszwAIex8WjHR6FLtrqMG8Jp4UV7ZK00jW2zsys=
40+
github.com/riverqueue/river/riverdriver v0.23.2-0.20250724035158-e771d8dbe258 h1:ypM21Ijo+YOfN7K+2uA0v+ko+Sp0C0oNtQsSfI30uSA=
41+
github.com/riverqueue/river/riverdriver v0.23.2-0.20250724035158-e771d8dbe258/go.mod h1:GN3r8XgDN/YwY1mudkPdrtyFTE3Pq/AMKrUePlcH0Uc=
42+
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.23.2-0.20250724035158-e771d8dbe258 h1:XdJVh2WipsCQmLEtBG0DqjTTjDCFB8HhEkuslbsUi6c=
43+
github.com/riverqueue/river/riverdriver/riverpgxv5 v0.23.2-0.20250724035158-e771d8dbe258/go.mod h1:zq7JUs9cRojrr9TnOlTDI4UUNUo5hSN6AFCw4CG/Utc=
44+
github.com/riverqueue/river/rivershared v0.23.2-0.20250724035158-e771d8dbe258 h1:tH8aT33UtnfazCK/Bd+WEPx0LJswesuE2sowqnyNbH0=
45+
github.com/riverqueue/river/rivershared v0.23.2-0.20250724035158-e771d8dbe258/go.mod h1:UsI58Hudx2g8XS0F0+nbzBAkwmejcqdSLC3weYYKKJs=
46+
github.com/riverqueue/river/rivertype v0.23.2-0.20250724035158-e771d8dbe258 h1:wVqKMMIBssfgbRBEO9/iECT5VW9+f9p42OP+tsmyl4s=
47+
github.com/riverqueue/river/rivertype v0.23.2-0.20250724035158-e771d8dbe258/go.mod h1:lmdl3vLNDfchDWbYdW2uAocIuwIN+ZaXqAukdSCFqWs=
3848
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
3949
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
4050
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=

0 commit comments

Comments
 (0)