Skip to content

Commit eea69b0

Browse files
committed
Add Go SDK, examples, CI/release pipeline, and docs
1 parent d6db750 commit eea69b0

File tree

9 files changed

+636
-4
lines changed

9 files changed

+636
-4
lines changed

.github/workflows/ci.yaml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v4
15+
16+
- name: Set up Go
17+
uses: actions/setup-go@v5
18+
with:
19+
go-version: "1.23.x"
20+
cache: true
21+
22+
- name: Install staticcheck
23+
run: go install honnef.co/go/tools/cmd/staticcheck@latest
24+
25+
- name: Go fmt / vet / test / staticcheck
26+
run: |
27+
go fmt ./...
28+
go vet ./...
29+
go test ./...
30+
$(go env GOPATH)/bin/staticcheck ./...
31+
32+
- name: Build container (no push)
33+
run: docker build -t ghcr.io/${{ github.repository }}/gamifykit:ci-${{ github.run_number }} .
34+

.github/workflows/release.yaml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
workflow_dispatch:
8+
9+
env:
10+
IMAGE_NAME: ghcr.io/${{ github.repository }}/gamifykit
11+
12+
jobs:
13+
release:
14+
runs-on: ubuntu-latest
15+
permissions:
16+
contents: read
17+
packages: write
18+
steps:
19+
- name: Checkout
20+
uses: actions/checkout@v4
21+
22+
- name: Set up Go
23+
uses: actions/setup-go@v5
24+
with:
25+
go-version: "1.23.x"
26+
cache: true
27+
28+
- name: Install staticcheck
29+
run: go install honnef.co/go/tools/cmd/staticcheck@latest
30+
31+
- name: Go fmt / vet / test / staticcheck
32+
run: |
33+
go fmt ./...
34+
go vet ./...
35+
go test ./...
36+
$(go env GOPATH)/bin/staticcheck ./...
37+
38+
- name: Log in to GHCR
39+
uses: docker/login-action@v3
40+
with:
41+
registry: ghcr.io
42+
username: ${{ github.actor }}
43+
password: ${{ secrets.GITHUB_TOKEN }}
44+
45+
- name: Build and push image
46+
run: |
47+
TAG=${GITHUB_REF_NAME#refs/tags/}
48+
IMAGE_TAG=${IMAGE_NAME}:${TAG}
49+
docker build -t ${IMAGE_TAG} .
50+
docker push ${IMAGE_TAG}
51+
52+
- name: Publish latest tag
53+
run: |
54+
docker tag ${IMAGE_NAME}:${GITHUB_REF_NAME#refs/tags/} ${IMAGE_NAME}:latest
55+
docker push ${IMAGE_NAME}:latest
56+

Dockerfile

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
FROM golang:1.23-alpine AS build
2+
3+
WORKDIR /src
4+
COPY go.mod go.sum ./
5+
RUN go mod download
6+
COPY . .
7+
8+
# Build static binary
9+
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/gamifykit ./cmd/gamifykit-server
10+
11+
FROM alpine:3.20
12+
RUN adduser -D -u 10001 appuser
13+
WORKDIR /app
14+
COPY --from=build /out/gamifykit /app/gamifykit
15+
USER appuser
16+
EXPOSE 8080
17+
ENTRYPOINT ["/app/gamifykit"]
18+

docs/REFACTORING_PLAN.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ Each phase builds upon the previous one, allowing for:
143143
- [x] Phase 2.1: HTTP API refactor
144144
- [x] Phase 2.2: Leaderboard implementation
145145
- [x] Phase 2.3: Analytics integration
146+
- [x] SDK + minimal OpenAPI + GHCR container pipeline
146147
- [ ] Phase 3.1: Dependency injection
147148
- [ ] Phase 3.2: Error handling
148149
- [ ] Phase 3.3: Advanced features
@@ -155,7 +156,7 @@ Each phase builds upon the previous one, allowing for:
155156
Core infrastructure is solid! We've completed production adapters, observability, basic HTTP API improvements, and leaderboard functionality. The system is now production-ready for basic gamification use cases.
156157

157158
Next logical steps:
158-
1. **Configuration System**: Environment-based configuration for production deployments
159-
2. **Analytics Integration**: Basic event tracking and metrics for gamification insights
160-
3. **Advanced HTTP API**: Authentication, rate limiting, and comprehensive error handling
161-
4. **Dependency Injection**: Clean service composition for better testability
159+
1. **Dependency Injection**: Clean service composition for better testability
160+
2. **Advanced HTTP API**: Authentication, rate limiting, and comprehensive error handling
161+
3. **Security & Auth**: AuthN/Z, rate limiting, and audit trails
162+
4. **Packaging**: Harden Helm/Compose/self-host artifacts after GHCR pipeline

docs/SDK_USAGE.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# GamifyKit SDK (Go) & Deployment Quickstart
2+
3+
## Install
4+
Add to your module:
5+
```bash
6+
go get gamifykit/sdk/go
7+
```
8+
9+
## Initialize the client
10+
```go
11+
import sdk "gamifykit/sdk/go"
12+
13+
client, err := sdk.NewClient("http://localhost:8080/api",
14+
sdk.WithAuthToken("your-token"), // optional
15+
)
16+
```
17+
18+
## Core calls
19+
- Add points: `client.AddPoints(ctx, "alice", 50, "xp")`
20+
- Award badge: `client.AwardBadge(ctx, "alice", "onboarded")`
21+
- Get state: `client.GetUser(ctx, "alice")`
22+
- Health: `client.Health(ctx)`
23+
- Realtime: `events, _ := client.SubscribeEvents(ctx); range events { ... }`
24+
25+
See `examples/sdk-go` for a runnable sample.
26+
27+
## Running the API via container
28+
Build or pull the image (published by the release workflow):
29+
```bash
30+
docker run -p 8080:8080 ghcr.io/OWNER/REPO/gamifykit:latest
31+
```
32+
33+
Environment hints (via config package):
34+
- `GAMIFYKIT_ENVIRONMENT`: development|production
35+
- `GAMIFYKIT_SERVER_ADDRESS`: default `:8080`
36+
- `GAMIFYKIT_SERVER_PATHPREFIX`: e.g., `/api`
37+
- `GAMIFYKIT_SERVER_CORSORIGIN`: e.g., `*`
38+
- `GAMIFYKIT_STORAGE_ADAPTER`: memory|redis|sql
39+
40+
For Redis/SQL adapters provide their respective env vars as defined in `config/README.md`.
41+

docs/openapi/gamifykit.yaml

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
openapi: 3.1.0
2+
info:
3+
title: GamifyKit API
4+
version: "0.1.0"
5+
servers:
6+
- url: http://localhost:8080/api
7+
paths:
8+
/healthz:
9+
get:
10+
summary: Health check
11+
responses:
12+
'200':
13+
description: Service is healthy
14+
content:
15+
application/json:
16+
schema:
17+
$ref: '#/components/schemas/Health'
18+
/users/{userId}:
19+
get:
20+
summary: Get user state
21+
parameters:
22+
- name: userId
23+
in: path
24+
required: true
25+
schema:
26+
type: string
27+
responses:
28+
'200':
29+
description: Current gamification state
30+
content:
31+
application/json:
32+
schema:
33+
$ref: '#/components/schemas/UserState'
34+
/users/{userId}/points:
35+
post:
36+
summary: Add points for a user
37+
parameters:
38+
- name: userId
39+
in: path
40+
required: true
41+
schema:
42+
type: string
43+
- name: metric
44+
in: query
45+
schema:
46+
type: string
47+
default: xp
48+
- name: delta
49+
in: query
50+
required: true
51+
schema:
52+
type: integer
53+
format: int64
54+
responses:
55+
'200':
56+
description: New total points
57+
content:
58+
application/json:
59+
schema:
60+
type: object
61+
properties:
62+
total:
63+
type: integer
64+
format: int64
65+
err:
66+
type: string
67+
nullable: true
68+
/users/{userId}/badges/{badge}:
69+
post:
70+
summary: Award a badge to a user
71+
parameters:
72+
- name: userId
73+
in: path
74+
required: true
75+
schema:
76+
type: string
77+
- name: badge
78+
in: path
79+
required: true
80+
schema:
81+
type: string
82+
responses:
83+
'200':
84+
description: Badge awarded
85+
content:
86+
application/json:
87+
schema:
88+
type: object
89+
properties:
90+
ok:
91+
type: boolean
92+
err:
93+
type: string
94+
nullable: true
95+
components:
96+
schemas:
97+
Health:
98+
type: object
99+
properties:
100+
status:
101+
type: string
102+
checks:
103+
type: object
104+
additionalProperties: true
105+
UserState:
106+
type: object
107+
properties:
108+
user_id:
109+
type: string
110+
points:
111+
type: object
112+
additionalProperties:
113+
type: integer
114+
format: int64
115+
badges:
116+
type: object
117+
additionalProperties:
118+
type: object
119+
levels:
120+
type: object
121+
additionalProperties:
122+
type: integer
123+
format: int64
124+
updated:
125+
type: string
126+
format: date-time
127+

examples/sdk-go/main.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"log"
7+
"time"
8+
9+
sdk "gamifykit/sdk/go"
10+
)
11+
12+
func main() {
13+
client, err := sdk.NewClient("http://localhost:8080/api")
14+
if err != nil {
15+
log.Fatalf("create client: %v", err)
16+
}
17+
18+
ctx := context.Background()
19+
userID := "alice"
20+
21+
// Add XP
22+
total, err := client.AddPoints(ctx, userID, 50, "xp")
23+
if err != nil {
24+
log.Fatalf("add points: %v", err)
25+
}
26+
fmt.Printf("User %s now has %d xp\n", userID, total)
27+
28+
// Award badge
29+
if err := client.AwardBadge(ctx, userID, "onboarded"); err != nil {
30+
log.Fatalf("award badge: %v", err)
31+
}
32+
33+
// Fetch state
34+
state, err := client.GetUser(ctx, userID)
35+
if err != nil {
36+
log.Fatalf("get user: %v", err)
37+
}
38+
fmt.Printf("State: points=%v badges=%v levels=%v updated=%s\n", state.Points, state.Badges, state.Levels, state.Updated.Format(time.RFC3339))
39+
40+
// Listen for realtime events for a short period
41+
eventsCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
42+
defer cancel()
43+
44+
events, err := client.SubscribeEvents(eventsCtx)
45+
if err != nil {
46+
log.Fatalf("subscribe: %v", err)
47+
}
48+
49+
for evt := range events {
50+
fmt.Printf("event: %s user=%s delta=%d total=%d badge=%s level=%d\n",
51+
evt.Type, evt.UserID, evt.Delta, evt.Total, evt.Badge, evt.Level)
52+
}
53+
}

0 commit comments

Comments
 (0)