Skip to content

Commit 238d94d

Browse files
Merge pull request #189 from flocko-motion/development
uli's request to deploy dev -> prod
2 parents f6bd74d + 7c72aff commit 238d94d

File tree

246 files changed

+141862
-13371
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

246 files changed

+141862
-13371
lines changed

.vscode/launch.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,5 +117,34 @@
117117
],
118118
"buildFlags": "-tags=ai_tests"
119119
},
120+
//
121+
{
122+
"name": "Debug TestGuestCanCreateSession",
123+
"type": "go",
124+
"request": "launch",
125+
"mode": "test",
126+
"program": "${workspaceFolder}/testing",
127+
"cwd": "${workspaceFolder}/testing",
128+
"args": [
129+
"-test.run",
130+
"TestPrivateShareSuite/TestGuestCanCreateSession",
131+
"-test.v"
132+
],
133+
"buildFlags": "-tags=ai_tests"
134+
},
135+
{
136+
"name": "Debug TestWorkshopIndividualSuite/TestSessionSystemMessageIncludesWorkshopPromptConstraints",
137+
"type": "go",
138+
"request": "launch",
139+
"mode": "test",
140+
"program": "${workspaceFolder}/testing",
141+
"cwd": "${workspaceFolder}/testing",
142+
"args": [
143+
"-test.run",
144+
"TestWorkshopIndividualSuite/TestSessionSystemMessageIncludesWorkshopPromptConstraints",
145+
"-test.v"
146+
],
147+
"buildFlags": "-tags=ai_tests"
148+
},
120149
]
121150
}

git-rebase.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ echo "Rebasing to origin/development..."
99

1010
git fetch origin development
1111
git rebase origin/development
12+
git pull
1213

1314
echo "✅ Rebased successfully."

run-test.sh

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,31 @@
99
# ./run-test.sh # Clean summary (default)
1010
# ./run-test.sh --verbose # Full verbose output
1111
# ./run-test.sh -v # Full verbose output (short)
12-
# ./run-test.sh --no-ai # Exclude AI tests
13-
# ./run-test.sh --no-ai --verbose # Exclude AI tests, verbose
12+
# ./run-test.sh --no-ai # Exclude AI tests (default)
13+
# ./run-test.sh --ai # Include AI tests
14+
# ./run-test.sh --only-ai # Run ONLY AI tests
15+
# ./run-test.sh --only-ai --verbose # Run only AI tests, verbose
1416

1517
cd "$(dirname "$0")"
1618

1719
GOTESTSUM="go run gotest.tools/gotestsum@latest"
1820

1921
# Parse arguments
2022
BUILD_TAGS=""
23+
TEST_RUN=""
2124
VERBOSE=false
2225
for arg in "$@"; do
2326
case "$arg" in
2427
--no-ai) ;; # default behavior, no-op
2528
--verbose|-v) VERBOSE=true ;;
2629
--ai) BUILD_TAGS="-tags ai_tests" ;;
30+
--only-ai) BUILD_TAGS="-tags ai_tests" ; TEST_RUN="-run TestGameEngineSuite" ;;
2731
esac
2832
done
2933

30-
if [[ -n "$BUILD_TAGS" ]]; then
34+
if [[ "$TEST_RUN" == "-run TestGameEngineSuite" ]]; then
35+
echo "🧪 Running ONLY AI tests..."
36+
elif [[ -n "$BUILD_TAGS" ]]; then
3137
echo "🧪 Running integration tests (including AI tests)..."
3238
else
3339
echo "🧪 Running integration tests (excluding AI tests)..."
@@ -42,9 +48,10 @@ if [[ -n "$CI" ]]; then
4248
elif $VERBOSE; then
4349
FORMAT="standard-verbose"
4450
else
45-
FORMAT="testname"
51+
# Show dots for progress, then detailed failure summary at the end
52+
FORMAT="dots-v2"
4653
fi
4754

48-
$GOTESTSUM --format "$FORMAT" -- -v $BUILD_TAGS ./...
55+
$GOTESTSUM --format "$FORMAT" --format-hide-empty-pkg -- -v $BUILD_TAGS $TEST_RUN ./...
4956

5057
exit $?

server/api/httpx/auth.go

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,6 @@ func getAuth0Validator() *validator.Validator {
6262
log.Error("failed to set up auth0 validator", "error", err)
6363
return nil
6464
}
65-
log.Debug("auth0 validator initialized", "issuer", issuerURL.String())
66-
6765
return auth0Validator
6866
}
6967

@@ -200,8 +198,6 @@ func Authenticate(next http.Handler) http.Handler {
200198
log.Error("failed to set up auth0 validator", "error", err)
201199
return nil
202200
}
203-
log.Debug("auth0 validator initialized", "issuer", issuerURL.String())
204-
205201
auth0Middleware = jwtmiddleware.New(
206202
jwtValidator.ValidateToken,
207203
jwtmiddleware.WithErrorHandler(func(w http.ResponseWriter, r *http.Request, err error) {
@@ -266,7 +262,6 @@ func Authenticate(next http.Handler) http.Handler {
266262
return
267263
}
268264

269-
log.Debug("participant token authenticated", "user_id", user.ID, "user_name", user.Name)
270265
next.ServeHTTP(w, WithUser(r, user))
271266
return
272267
}
@@ -289,7 +284,6 @@ func Authenticate(next http.Handler) http.Handler {
289284
}
290285

291286
user = db.CheckAndPromoteAdmin(r.Context(), user)
292-
log.Debug("CGL JWT authenticated", "user_id", userId, "user_name", user.Name)
293287
next.ServeHTTP(w, WithUser(r, user))
294288
return
295289
}
@@ -321,21 +315,25 @@ func Authenticate(next http.Handler) http.Handler {
321315
token := tokenObj.(*validator.ValidatedClaims)
322316
auth0ID := token.RegisteredClaims.Subject
323317
if auth0ID == "" {
318+
log.Warn("auth0 token has empty subject claim")
324319
next.ServeHTTP(w, r)
325320
return
326321
}
327322

323+
log.Debug("auth0 token validated", "auth0_id", auth0ID, "auth0_id_length", len(auth0ID))
324+
328325
// Load user by Auth0 ID - do NOT auto-create
329326
user, err := db.GetUserByAuth0ID(r.Context(), auth0ID)
330327
if err != nil {
331328
// User not registered - frontend will get email/name from Auth0 directly
332-
log.Debug("auth0 user not registered", "auth0_id", auth0ID)
329+
log.Debug("auth0 user not registered", "auth0_id", auth0ID, "error", err)
333330
WriteUserNotRegistered(w, auth0ID)
334331
return
335332
}
336333

334+
log.Debug("auth0 user found", "auth0_id", auth0ID, "user_id", user.ID, "user_name", user.Name)
335+
337336
user = db.CheckAndPromoteAdmin(r.Context(), user)
338-
log.Debug("auth0 authenticated", "auth0_id", auth0ID, "user_name", user.Name)
339337
next.ServeHTTP(w, WithUser(r, user))
340338
})).ServeHTTP(w, r)
341339
})
@@ -399,8 +397,6 @@ func RequireAuth0Token(h http.HandlerFunc) http.Handler {
399397
return
400398
}
401399

402-
log.Debug("auth0 token validated for registration", "auth0_id", auth0ID)
403-
404400
// Store Auth0 ID in context for the handler to use
405401
ctx := context.WithValue(r.Context(), auth0IDContextKey, auth0ID)
406402
h.ServeHTTP(w, r.WithContext(ctx))

server/api/httpx/middleware.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,13 @@ func Logging(next http.Handler) http.Handler {
6363

6464
next.ServeHTTP(wrapped, r)
6565

66+
// Skip logging CORS preflight requests
67+
if r.Method == http.MethodOptions {
68+
return
69+
}
70+
6671
duration := time.Since(start)
67-
log.Info("http request",
68-
"method", r.Method,
69-
"path", r.URL.Path,
70-
"status", wrapped.status,
71-
"duration", duration,
72-
)
72+
log.Info(fmt.Sprintf("%s %s → %d (%s)", r.Method, r.URL.Path, wrapped.status, formatDuration(duration)))
7373

7474
// Report error responses (4xx/5xx) to Sentry
7575
if wrapped.status >= 400 {
@@ -165,6 +165,18 @@ func truncateBody(b []byte) string {
165165
return string(b)
166166
}
167167

168+
// formatDuration formats a duration in a compact human-readable form.
169+
func formatDuration(d time.Duration) string {
170+
switch {
171+
case d < time.Millisecond:
172+
return fmt.Sprintf("%dµs", d.Microseconds())
173+
case d < time.Second:
174+
return fmt.Sprintf("%dms", d.Milliseconds())
175+
default:
176+
return fmt.Sprintf("%.2fs", d.Seconds())
177+
}
178+
}
179+
168180
// flattenHeaders converts http.Header to a simple map for Sentry extras.
169181
// Redacts the Authorization header.
170182
func flattenHeaders(h http.Header) map[string]string {

server/api/routes/auth.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,27 @@ func RegisterUser(w http.ResponseWriter, r *http.Request) {
7676
}
7777

7878
// Check if user already exists with this Auth0 ID
79-
existingUser, _ := db.GetUserByAuth0ID(r.Context(), auth0ID)
79+
log.Debug("checking if user exists by auth0_id", "auth0_id", auth0ID, "email", email, "name", name)
80+
existingUser, err := db.GetUserByAuth0ID(r.Context(), auth0ID)
8081
if existingUser != nil {
82+
log.Warn("user already registered with this auth0_id", "auth0_id", auth0ID, "user_id", existingUser.ID, "existing_email", existingUser.Email)
8183
httpx.WriteError(w, http.StatusBadRequest, "User already registered")
8284
return
8385
}
86+
if err != nil {
87+
log.Debug("no user found by auth0_id (expected for new registration)", "auth0_id", auth0ID, "error", err)
88+
}
89+
90+
// Also check if a user exists with this email (might be a duplicate or auth0_id mismatch)
91+
existingByEmail, emailErr := db.GetUserByEmail(r.Context(), email)
92+
if emailErr == nil && existingByEmail != nil {
93+
log.Error("user with this email already exists",
94+
"email", email,
95+
"existing_user_id", existingByEmail.ID,
96+
"existing_auth0_id", existingByEmail.Auth0Id,
97+
"new_auth0_id", auth0ID,
98+
"auth0_ids_match", existingByEmail.Auth0Id != nil && *existingByEmail.Auth0Id == auth0ID)
99+
}
84100

85101
// Check if name is already taken
86102
nameTaken, err := db.IsNameTaken(r.Context(), name)
@@ -95,9 +111,10 @@ func RegisterUser(w http.ResponseWriter, r *http.Request) {
95111
}
96112

97113
// Create the user
114+
log.Info("attempting to create user", "name", name, "email", email, "auth0_id", auth0ID)
98115
user, err := db.CreateUser(r.Context(), name, &email, auth0ID)
99116
if err != nil {
100-
log.Error("failed to create user", "error", err)
117+
log.Error("failed to create user", "name", name, "email", email, "auth0_id", auth0ID, "error", err)
101118
httpx.WriteError(w, http.StatusInternalServerError, "Failed to create user")
102119
return
103120
}

server/api/routes/games.go

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ func GetGames(w http.ResponseWriter, r *http.Request) {
4444
sortDir := r.URL.Query().Get("sortDir")
4545
filter := r.URL.Query().Get("filter")
4646

47-
log.Debug("listing games", "user_id", userID, "search", searchQuery, "sortBy", sortBy, "sortDir", sortDir, "filter", filter)
4847
filters := &db.GetGamesFilters{
4948
Search: searchQuery,
5049
SortField: sortBy,
@@ -105,18 +104,14 @@ func CreateGame(w http.ResponseWriter, r *http.Request) {
105104
game.Public = *req.Public
106105
}
107106

108-
log.Debug("creating game", "user_id", user.ID, "name", game.Name)
109107
if err := db.CreateGame(r.Context(), user.ID, &game); err != nil {
110-
log.Debug("game creation failed", "error", err)
111108
if appErr, ok := err.(*obj.AppError); ok {
112109
httpx.WriteAppError(w, appErr)
113110
return
114111
}
115112
httpx.WriteError(w, http.StatusInternalServerError, "Failed to create game: "+err.Error())
116113
return
117114
}
118-
log.Debug("game created", "game_id", game.ID)
119-
120115
created, err := db.GetGameByID(r.Context(), &user.ID, game.ID)
121116
if err != nil {
122117
httpx.WriteError(w, http.StatusInternalServerError, "Failed to load created game: "+err.Error())
@@ -150,8 +145,6 @@ func GetGameByID(w http.ResponseWriter, r *http.Request) {
150145
userID = &user.ID
151146
}
152147

153-
log.Debug("getting game by ID", "game_id", gameID, "user_id", userID)
154-
155148
game, err := db.GetGameByID(r.Context(), userID, gameID)
156149
if err != nil {
157150
httpx.WriteError(w, http.StatusNotFound, "Game not found")
@@ -185,8 +178,6 @@ func UpdateGame(w http.ResponseWriter, r *http.Request) {
185178

186179
user := httpx.UserFromRequest(r)
187180

188-
log.Debug("updating game", "game_id", gameID, "user_id", user.ID)
189-
190181
var updatedGame obj.Game
191182
if err := httpx.ReadJSON(r, &updatedGame); err != nil {
192183
httpx.WriteError(w, http.StatusBadRequest, "Invalid JSON: "+err.Error())
@@ -240,21 +231,16 @@ func DeleteGame(w http.ResponseWriter, r *http.Request) {
240231

241232
user := httpx.UserFromRequest(r)
242233

243-
log.Debug("deleting game", "game_id", gameID, "user_id", user.ID)
244-
245234
deleted, err := db.GetGameByID(r.Context(), &user.ID, gameID)
246235
if err != nil {
247236
httpx.WriteError(w, http.StatusNotFound, "Game not found")
248237
return
249238
}
250239

251240
if err := db.DeleteGame(r.Context(), user.ID, gameID); err != nil {
252-
log.Debug("game deletion failed", "game_id", gameID, "error", err)
253241
httpx.WriteError(w, http.StatusInternalServerError, "Failed to delete game: "+err.Error())
254242
return
255243
}
256-
log.Debug("game deleted", "game_id", gameID)
257-
258244
httpx.WriteJSON(w, http.StatusOK, deleted)
259245
}
260246

@@ -281,8 +267,6 @@ func CloneGame(w http.ResponseWriter, r *http.Request) {
281267

282268
user := httpx.UserFromRequest(r)
283269

284-
log.Debug("cloning game", "game_id", gameID, "user_id", user.ID)
285-
286270
// Get the source game (allow cloning public games or own games)
287271
sourceGame, err := db.GetGameByID(r.Context(), nil, gameID)
288272
if err != nil {
@@ -321,18 +305,13 @@ func CloneGame(w http.ResponseWriter, r *http.Request) {
321305
OriginallyCreatedBy: originalCreator,
322306
}
323307

324-
log.Debug("creating cloned game", "user_id", user.ID, "source_game_id", gameID, "name", clonedGame.Name)
325308
if err := db.CreateGame(r.Context(), user.ID, &clonedGame); err != nil {
326-
log.Debug("game clone failed", "error", err)
327309
httpx.WriteError(w, http.StatusInternalServerError, "Failed to clone game: "+err.Error())
328310
return
329311
}
330-
log.Debug("game cloned", "new_game_id", clonedGame.ID)
331-
332312
// Increment the clone count on the source game
333313
if err := db.IncrementGameCloneCount(r.Context(), gameID); err != nil {
334-
log.Debug("failed to increment clone count", "error", err)
335-
// Don't fail the request, just log the error
314+
log.Warn("failed to increment clone count", "error", err)
336315
}
337316

338317
created, err := db.GetGameByID(r.Context(), &user.ID, clonedGame.ID)

server/api/routes/games_favourites.go

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55

66
"cgl/api/httpx"
77
"cgl/db"
8-
"cgl/log"
98
)
109

1110
// GetFavouriteGames godoc
@@ -22,8 +21,6 @@ import (
2221
func GetFavouriteGames(w http.ResponseWriter, r *http.Request) {
2322
user := httpx.UserFromRequest(r)
2423

25-
log.Debug("getting favourite games", "user_id", user.ID)
26-
2724
games, err := db.GetFavouriteGames(r.Context(), user.ID)
2825
if err != nil {
2926
httpx.WriteError(w, http.StatusInternalServerError, "Failed to get favourite games: "+err.Error())
@@ -55,8 +52,6 @@ func AddFavouriteGame(w http.ResponseWriter, r *http.Request) {
5552

5653
user := httpx.UserFromRequest(r)
5754

58-
log.Debug("adding favourite game", "game_id", gameID, "user_id", user.ID)
59-
6055
if err := db.AddFavouriteGame(r.Context(), user.ID, gameID); err != nil {
6156
httpx.WriteError(w, http.StatusInternalServerError, "Failed to add favourite: "+err.Error())
6257
return
@@ -87,8 +82,6 @@ func RemoveFavouriteGame(w http.ResponseWriter, r *http.Request) {
8782

8883
user := httpx.UserFromRequest(r)
8984

90-
log.Debug("removing favourite game", "game_id", gameID, "user_id", user.ID)
91-
9285
if err := db.RemoveFavouriteGame(r.Context(), user.ID, gameID); err != nil {
9386
httpx.WriteError(w, http.StatusInternalServerError, "Failed to remove favourite: "+err.Error())
9487
return

0 commit comments

Comments
 (0)