Skip to content

Commit 4c1aecc

Browse files
committed
fix: honest version stamping + API Docs link in web sidebar
Versioning (make deploy was always tagging v1.0.0): - docker-compose.yml & docker-compose.external.yml: add args block reading VERSION/BUILD_DATE/DEFAULT_SERVER_URL from env, so direct 'docker compose up --build' (not just 'make deploy') stamps the git-derived version. Image tag now uses ${VERSION:-latest}. - Makefile: export VERSION/BUILD_DATE/DEFAULT_SERVER_URL so compose sees them; build-server target now passes -ldflags '-X main.Version=... -X main.BuildDate=...' (was missing, only build-cli/build-linux/release had it). - cmd/server/main.go: default Version 'dev' instead of stale '1.7.0' so unflagged builds are obviously unversioned. Web UI: - SidebarLayout: add 'API Docs' entry linking to /docs with the JWT in ?token= query string (Scalar UI propagates it). Opens in new tab. Uses BookOpen + ExternalLink icons. OAuth login icons: already implemented in Login.tsx — buttons render conditionally from /auth/providers response. To enable, set OAUTH_GOOGLE_CLIENT_ID / OAUTH_GITHUB_CLIENT_ID (and matching SECRET) on the server; icons appear automatically.
1 parent 4cbc126 commit 4c1aecc

7 files changed

Lines changed: 84 additions & 17 deletions

File tree

Makefile

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ BUILD_DATE ?= $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
1414
DEFAULT_SERVER_URL ?= http://localhost:8000
1515
SKIP_WEB ?= 0
1616

17+
# Export so docker compose's `${VERSION}` / `${BUILD_DATE}` interpolation in
18+
# docker-compose*.yml picks up the git-derived values automatically — even
19+
# when the user runs `docker compose up --build` directly without going
20+
# through `make deploy`.
21+
export VERSION
22+
export BUILD_DATE
23+
export DEFAULT_SERVER_URL
24+
1725
COMPOSE = docker compose --env-file docker/.env -f docker/docker-compose.yml
1826
COMPOSE_DEV = $(COMPOSE) -f docker/docker-compose.dev.yml
1927
COMPOSE_EXT = docker compose --env-file docker/.env -f docker/docker-compose.external.yml
@@ -52,8 +60,9 @@ prepare-assets: ## Prepare web assets (skip with SKIP_WEB=1)
5260

5361
build-server: prepare-assets ## Build Go server binary
5462
@mkdir -p build/binaries
55-
@go build -o $(SERVER_BINARY) ./cmd/server
56-
@echo "✅ Server: $(SERVER_BINARY)"
63+
@go build -ldflags "-X main.Version=$(VERSION) -X main.BuildDate=$(BUILD_DATE)" \
64+
-o $(SERVER_BINARY) ./cmd/server
65+
@echo "✅ Server: $(SERVER_BINARY) ($(VERSION))"
5766

5867
build-cli: ## Build datumctl CLI tool
5968
@mkdir -p build/binaries

cmd/server/main.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ import (
4545
var webFS embed.FS
4646

4747
var (
48-
Version = "1.7.0"
48+
// Version is overridden at build time via -ldflags. The default is a
49+
// "dev" sentinel so a binary built without `make build` (e.g. plain
50+
// `go run ./cmd/server`) is obviously unversioned instead of pretending
51+
// to be a stale tagged release.
52+
Version = "dev"
4953
BuildDate = "unknown"
5054
)
5155

docker/docker-compose.external.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
services:
22
datum-server:
3-
image: datum-server:latest
3+
image: datum-server:${VERSION:-latest}
44
build:
55
context: ..
66
dockerfile: docker/Dockerfile
7+
args:
8+
# Sourced from the env (typically exported by the Makefile from
9+
# `git describe --tags --always --dirty`). Falls back to a sentinel
10+
# so a misconfigured deploy is obvious instead of silently shipping
11+
# the Dockerfile default of `1.0.0`.
12+
VERSION: ${VERSION:-unknown}
13+
BUILD_DATE: ${BUILD_DATE:-unknown}
14+
DEFAULT_SERVER_URL: ${PUBLIC_URL:-http://localhost:8000}
715
container_name: datum-server
816
restart: unless-stopped
917
ports:

docker/docker-compose.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,14 @@ services:
3636

3737
# Go API Server (Main backend)
3838
datum-server:
39-
image: datum-server:latest
39+
image: datum-server:${VERSION:-latest}
4040
build:
4141
context: ..
4242
dockerfile: docker/Dockerfile
43+
args:
44+
VERSION: ${VERSION:-unknown}
45+
BUILD_DATE: ${BUILD_DATE:-unknown}
46+
DEFAULT_SERVER_URL: ${PUBLIC_URL:-http://localhost:8000}
4347
container_name: datum-server
4448
volumes:
4549
- ${DATA_DIR:-./data}:/root/data

web/src/features/auth/services/authService.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,11 @@ export const authService = {
4949
await api.delete(`/auth/keys/${id}`);
5050
},
5151

52-
changePassword: async (password: string): Promise<void> => {
53-
await api.put('/auth/password', { password });
52+
changePassword: async (oldPassword: string, newPassword: string): Promise<void> => {
53+
await api.put('/auth/password', {
54+
old_password: oldPassword,
55+
new_password: newPassword,
56+
});
5457
},
5558

5659
forgotPassword: async (email: string): Promise<void> => {

web/src/features/settings/pages/Settings.tsx

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -313,26 +313,34 @@ export default function Settings() {
313313
// Could add a toast here
314314
};
315315

316+
const [oldPassword, setOldPassword] = useState('');
316317
const [newPassword, setNewPassword] = useState('');
317318
const passwordMutation = useMutation({
318-
mutationFn: authService.changePassword,
319+
mutationFn: ({ oldPassword, newPassword }: { oldPassword: string; newPassword: string }) =>
320+
authService.changePassword(oldPassword, newPassword),
319321
onSuccess: () => {
320-
alert("Password updated successfully");
322+
alert('Password updated successfully. Other sessions have been signed out.');
323+
setOldPassword('');
321324
setNewPassword('');
322325
},
323-
onError: () => {
324-
alert("Failed to update password");
325-
}
326+
onError: (err: any) => {
327+
const msg = err?.response?.data?.error || 'Failed to update password';
328+
alert(msg);
329+
},
326330
});
327331

328332
const handleChangePassword = (e: React.FormEvent) => {
329333
e.preventDefault();
334+
if (!oldPassword) {
335+
alert('Please enter your current password');
336+
return;
337+
}
330338
if (newPassword.length < 8) {
331-
alert("Password must be at least 8 characters");
339+
alert('New password must be at least 8 characters');
332340
return;
333341
}
334-
if (window.confirm("Are you sure you want to change your password?")) {
335-
passwordMutation.mutate(newPassword);
342+
if (window.confirm('Are you sure you want to change your password?')) {
343+
passwordMutation.mutate({ oldPassword, newPassword });
336344
}
337345
};
338346

@@ -499,6 +507,17 @@ export default function Settings() {
499507
<div className="space-y-4">
500508
<h3 className="text-lg font-medium">Change Password</h3>
501509
<form onSubmit={handleChangePassword} className="space-y-4 max-w-sm">
510+
<div className="space-y-2">
511+
<label htmlFor="old-password">Current Password</label>
512+
<Input
513+
id="old-password"
514+
type="password"
515+
value={oldPassword}
516+
onChange={(e) => setOldPassword(e.target.value)}
517+
placeholder="Your current password"
518+
autoComplete="current-password"
519+
/>
520+
</div>
502521
<div className="space-y-2">
503522
<label htmlFor="new-password">New Password</label>
504523
<Input
@@ -507,9 +526,10 @@ export default function Settings() {
507526
value={newPassword}
508527
onChange={(e) => setNewPassword(e.target.value)}
509528
placeholder="Min. 8 characters"
529+
autoComplete="new-password"
510530
/>
511531
</div>
512-
<Button type="submit" disabled={!newPassword || passwordMutation.isPending}>
532+
<Button type="submit" disabled={!oldPassword || !newPassword || passwordMutation.isPending}>
513533
{passwordMutation.isPending ? 'Updating...' : 'Update Password'}
514534
</Button>
515535
</form>

web/src/layouts/SidebarLayout.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import {
55
Activity,
66
LogOut,
77
Menu,
8-
Settings as SettingsIcon
8+
Settings as SettingsIcon,
9+
BookOpen,
10+
ExternalLink
911
} from "lucide-react";
1012
import { useState, useEffect } from "react";
1113
import { cn } from "@/lib/utils";
@@ -76,6 +78,23 @@ export default function SidebarLayout() {
7678
{item.label}
7779
</NavLink>
7880
))}
81+
82+
{/* API Docs (server-rendered, JWT-gated via ?token=) */}
83+
<a
84+
href={`/docs${
85+
typeof window !== "undefined" && localStorage.getItem("datum_token")
86+
? `?token=${encodeURIComponent(localStorage.getItem("datum_token") || "")}`
87+
: ""
88+
}`}
89+
target="_blank"
90+
rel="noopener noreferrer"
91+
onClick={() => setIsOpen(false)}
92+
className="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-muted-foreground hover:bg-muted hover:text-foreground transition-colors"
93+
>
94+
<BookOpen className="h-5 w-5" />
95+
<span className="flex-1">API Docs</span>
96+
<ExternalLink className="h-3.5 w-3.5 opacity-60" />
97+
</a>
7998
</nav>
8099

81100
<div className="border-t p-4 space-y-2">

0 commit comments

Comments
 (0)