Skip to content

Commit ba1444c

Browse files
feat: admin UI with dual-variant release
1 parent a702e27 commit ba1444c

35 files changed

Lines changed: 2569 additions & 8 deletions

.github/workflows/release.yml

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ jobs:
1010
runs-on: ubuntu-latest
1111
permissions:
1212
contents: write
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
variant:
17+
- name: ui
18+
tags: ui
19+
suffix: ""
20+
- name: headless
21+
tags: ""
22+
suffix: "-headless"
1323
steps:
1424
- name: Checkout
1525
uses: actions/checkout@v4
@@ -18,24 +28,61 @@ jobs:
1828
with:
1929
go-version-file: "go.mod"
2030

21-
- name: Build
22-
run: go build -o blossom-linux-amd64 ./cmd/api/main.go
31+
- name: Setup pnpm
32+
if: matrix.variant.name == 'ui'
33+
uses: pnpm/action-setup@v4
34+
with:
35+
version: 9
36+
37+
- name: Setup Node
38+
if: matrix.variant.name == 'ui'
39+
uses: actions/setup-node@v4
40+
with:
41+
node-version: "22"
42+
cache: pnpm
43+
cache-dependency-path: ui/pnpm-lock.yaml
44+
45+
- name: Build UI
46+
if: matrix.variant.name == 'ui'
47+
working-directory: ui
48+
run: |
49+
pnpm install --frozen-lockfile
50+
pnpm build
51+
52+
- name: Build binary
53+
run: |
54+
go build -tags "${{ matrix.variant.tags }}" \
55+
-o blossom-linux-amd64${{ matrix.variant.suffix }} \
56+
./cmd/api/main.go
2357
2458
- name: Release
2559
uses: softprops/action-gh-release@v2
2660
with:
2761
token: ${{ secrets.GITHUB_TOKEN }}
28-
files: blossom-linux-amd64
62+
files: blossom-linux-amd64${{ matrix.variant.suffix }}
2963

3064
docker:
3165
runs-on: ubuntu-latest
3266
permissions:
3367
contents: read
3468
packages: write
69+
strategy:
70+
fail-fast: false
71+
matrix:
72+
variant:
73+
- name: ui
74+
target: runtime-ui
75+
suffix: ""
76+
- name: headless
77+
target: runtime-headless
78+
suffix: "-headless"
3579
steps:
3680
- name: Checkout
3781
uses: actions/checkout@v4
3882

83+
- name: Set up Buildx
84+
uses: docker/setup-buildx-action@v3
85+
3986
- name: Log in to ghcr.io
4087
uses: docker/login-action@v3
4188
with:
@@ -48,6 +95,8 @@ jobs:
4895
uses: docker/metadata-action@v5
4996
with:
5097
images: ghcr.io/${{ github.repository }}
98+
flavor: |
99+
suffix=${{ matrix.variant.suffix }},onlatest=true
51100
tags: |
52101
type=semver,pattern={{version}}
53102
type=semver,pattern={{major}}.{{minor}}
@@ -57,5 +106,8 @@ jobs:
57106
with:
58107
context: .
59108
push: true
109+
target: ${{ matrix.variant.target }}
60110
tags: ${{ steps.meta.outputs.tags }}
61111
labels: ${{ steps.meta.outputs.labels }}
112+
cache-from: type=gha,scope=${{ matrix.variant.name }}
113+
cache-to: type=gha,mode=max,scope=${{ matrix.variant.name }}

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,9 @@
33
db/**.sqlite3
44
config.yml
55
blossom-server
6+
bin/
7+
8+
# Admin UI build artifacts (embedded only when building with -tags ui)
9+
ui/node_modules/
10+
ui/build/
11+
ui/.svelte-kit/

Dockerfile

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,42 @@
1-
FROM dhi.io/golang:1.26-dev AS builder
1+
# syntax=docker/dockerfile:1.7
2+
3+
# --- Admin UI build ---
4+
FROM node:22-alpine AS ui-builder
5+
WORKDIR /src/ui
6+
RUN corepack enable
7+
COPY ui/package.json ui/pnpm-lock.yaml* ./
8+
RUN pnpm install --frozen-lockfile
9+
COPY ui/ ./
10+
RUN pnpm build
11+
12+
# --- Go build, headless (no UI embedded) ---
13+
FROM dhi.io/golang:1.26-dev AS builder-headless
14+
WORKDIR /go/src/app
15+
COPY . .
16+
RUN mkdir -p ./bin && CGO_ENABLED=1 go build \
17+
-ldflags "-linkmode external -extldflags '-static'" \
18+
-o ./bin/app ./cmd/api/main.go
19+
20+
# --- Go build, with UI embedded ---
21+
FROM dhi.io/golang:1.26-dev AS builder-ui
222
WORKDIR /go/src/app
323
COPY . .
4-
RUN mkdir ./bin && CGO_ENABLED=1 go build \
24+
COPY --from=ui-builder /src/ui/build ./ui/build
25+
RUN mkdir -p ./bin && CGO_ENABLED=1 go build \
26+
-tags ui \
527
-ldflags "-linkmode external -extldflags '-static'" \
628
-o ./bin/app ./cmd/api/main.go
729

8-
FROM dhi.io/static:20250419
9-
COPY --from=builder /go/src/app/bin/app /app
10-
COPY --from=builder /go/src/app/db /db
30+
# --- Runtime, headless ---
31+
FROM dhi.io/static:20250419 AS runtime-headless
32+
COPY --from=builder-headless /go/src/app/bin/app /app
33+
COPY --from=builder-headless /go/src/app/db /db
34+
EXPOSE 8000/tcp
35+
CMD ["/app"]
36+
37+
# --- Runtime, with UI (default target) ---
38+
FROM dhi.io/static:20250419 AS runtime-ui
39+
COPY --from=builder-ui /go/src/app/bin/app /app
40+
COPY --from=builder-ui /go/src/app/db /db
1141
EXPOSE 8000/tcp
1242
CMD ["/app"]

db/blobs.sql.go

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

db/queries/blobs.sql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ where pubkey = ?
1111
and created < ?
1212
order by created asc;
1313

14+
-- name: GetAllBlobsPaginated :many
15+
select *
16+
from blobs
17+
where created > ?
18+
and created < ?
19+
order by created desc;
20+
1421
-- name: GetBlobFromHash :one
1522
select *
1623
from blobs

internal/bud02/list.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,12 @@ func ListBlobs(
1515
) ([]*core.Blob, error) {
1616
return services.Blob().GetFromPubkeyPaginated(ctx, pubkey, since, until)
1717
}
18+
19+
func ListAllBlobs(
20+
ctx context.Context,
21+
services core.Services,
22+
since int64,
23+
until int64,
24+
) ([]*core.Blob, error) {
25+
return services.Blob().GetAllPaginated(ctx, since, until)
26+
}

internal/core/blob_descriptor.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,6 @@ type BlobStorage interface {
5353
GetFromHash(ctx context.Context, sha256 string) (*Blob, error)
5454
GetFromPubkey(ctx context.Context, pubkey string) ([]*Blob, error)
5555
GetFromPubkeyPaginated(ctx context.Context, pubkey string, since int64, until int64) ([]*Blob, error)
56+
GetAllPaginated(ctx context.Context, since int64, until int64) ([]*Blob, error)
5657
DeleteFromHash(ctx context.Context, sha256 string) error
5758
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package httpapi
2+
3+
import (
4+
"math"
5+
"net/http"
6+
"strconv"
7+
8+
"github.com/gin-gonic/gin"
9+
10+
bud02 "github.com/sebdeveloper6952/blossom-server/v2/internal/bud02"
11+
"github.com/sebdeveloper6952/blossom-server/v2/internal/core"
12+
)
13+
14+
type adminBlobDescriptor struct {
15+
Pubkey string `json:"pubkey"`
16+
Url string `json:"url"`
17+
Sha256 string `json:"sha256"`
18+
Size int64 `json:"size"`
19+
Type string `json:"type"`
20+
Uploaded int64 `json:"uploaded"`
21+
}
22+
23+
func fromDomainAdminBlobDescriptor(blob *core.Blob) adminBlobDescriptor {
24+
return adminBlobDescriptor{
25+
Pubkey: blob.Pubkey,
26+
Url: blob.Url,
27+
Sha256: blob.Sha256,
28+
Size: blob.Size,
29+
Type: blob.Type,
30+
Uploaded: blob.Uploaded,
31+
}
32+
}
33+
34+
func listAllBlobs(services core.Services) gin.HandlerFunc {
35+
return func(ctx *gin.Context) {
36+
var since int64
37+
var until int64 = math.MaxInt64
38+
39+
if s := ctx.Query("since"); s != "" {
40+
if v, err := strconv.ParseInt(s, 10, 64); err == nil {
41+
since = v
42+
}
43+
}
44+
if u := ctx.Query("until"); u != "" {
45+
if v, err := strconv.ParseInt(u, 10, 64); err == nil {
46+
until = v
47+
}
48+
}
49+
50+
blobs, err := bud02.ListAllBlobs(
51+
ctx.Request.Context(),
52+
services,
53+
since,
54+
until,
55+
)
56+
if err != nil {
57+
ctx.AbortWithStatusJSON(
58+
http.StatusInternalServerError,
59+
apiError{Message: err.Error()},
60+
)
61+
return
62+
}
63+
64+
out := make([]adminBlobDescriptor, len(blobs))
65+
for i := range blobs {
66+
out[i] = fromDomainAdminBlobDescriptor(blobs[i])
67+
}
68+
69+
ctx.JSON(http.StatusOK, out)
70+
}
71+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package httpapi
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/gin-gonic/gin"
7+
)
8+
9+
func adminOnlyMiddleware(adminPubkey string) gin.HandlerFunc {
10+
return func(c *gin.Context) {
11+
if adminPubkey == "" || c.GetString("pk") != adminPubkey {
12+
c.AbortWithStatus(http.StatusForbidden)
13+
return
14+
}
15+
c.Next()
16+
}
17+
}

internal/httpapi/me_controller.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package httpapi
2+
3+
import (
4+
"net/http"
5+
6+
"github.com/gin-gonic/gin"
7+
)
8+
9+
type meResponse struct {
10+
Pubkey string `json:"pubkey"`
11+
IsAdmin bool `json:"is_admin"`
12+
}
13+
14+
func getMe(adminPubkey string) gin.HandlerFunc {
15+
return func(ctx *gin.Context) {
16+
pk := ctx.GetString("pk")
17+
ctx.JSON(http.StatusOK, meResponse{
18+
Pubkey: pk,
19+
IsAdmin: adminPubkey != "" && pk == adminPubkey,
20+
})
21+
}
22+
}

0 commit comments

Comments
 (0)