mirror of
https://github.com/lukaszraczylo/claude-mnemonic.git
synced 2026-06-09 23:59:40 +00:00
d04b60517a
* Make things 'betterer' across the board * fix: reorganize struct fields and config parameters for consistency - [x] Reorder Config struct fields alphabetically and by related functionality - [x] Reorganize Observation model fields with archival fields grouped together - [x] Reorder ObservationStore fields to group related members - [x] Reorder Store struct fields with health check caching grouped - [x] Reorganize HealthInfo and PoolMetrics struct field order - [x] Reorder maintenance Service struct fields logically - [x] Reorganize MCP server handler parameter structs alphabetically - [x] Reorder pattern detector candidate tracking fields - [x] Reorganize search Manager struct fields by functionality - [x] Reorder vector Client struct fields with mutex protections grouped - [x] Reorganize handler request/response struct fields - [x] Update handlers_test.go to expect wrapped response format - [x] Reorder middleware TokenAuth and rate limiter fields - [x] Reorganize Service struct fields with grouped functionality - [x] Fix RateLimiter field ordering for clarity - [x] Reorder CircuitBreaker metrics fields * fix(security): improve JSON output safety and path traversal protection - [x] Replace unsafe JSON string formatting with proper json.Marshal in export handler - [x] Remove escapeJSONString helper function in favor of standard JSON marshaling - [x] Add safeResolvePath function to validate paths and prevent directory traversal - [x] Apply path traversal validation in captureFileMtimes operations - [x] Cap result slice capacity in getRecentSearchQueries to prevent DoS via excessive allocation * fix(sdk): improve path traversal protection and allocation safety - [x] Enhance safeResolvePath with stricter validation using filepath.Rel - [x] Reject paths containing ".." after cleaning to prevent traversal - [x] Validate absolute paths are within cwd when cwd is specified - [x] Apply safeResolvePath validation to GetFileContent for consistency - [x] Add comprehensive test coverage for path traversal protection - [x] Fix allocation safety in getRecentSearchQueries by using constant capacity
102 lines
3.0 KiB
Go
102 lines
3.0 KiB
Go
// Package gorm provides GORM-based database operations for claude-mnemonic.
|
|
package gorm
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
// EnsureSessionExists creates a session if it doesn't exist.
|
|
// Uses INSERT OR IGNORE pattern for atomic idempotent creation (single query instead of COUNT + INSERT).
|
|
// This is shared between stores to avoid duplication.
|
|
func EnsureSessionExists(ctx context.Context, db *gorm.DB, sdkSessionID, project string) error {
|
|
now := time.Now()
|
|
session := &SDKSession{
|
|
ClaudeSessionID: sdkSessionID,
|
|
SDKSessionID: sqlNullString(sdkSessionID),
|
|
Project: project,
|
|
Status: "active",
|
|
StartedAt: now.Format(time.RFC3339),
|
|
StartedAtEpoch: now.UnixMilli(),
|
|
PromptCounter: 0,
|
|
}
|
|
|
|
// Use INSERT OR IGNORE - single query, atomic operation
|
|
// If session already exists (conflict on sdk_session_id), do nothing
|
|
return db.WithContext(ctx).
|
|
Clauses(clause.OnConflict{
|
|
Columns: []clause.Column{{Name: "sdk_session_id"}},
|
|
DoNothing: true,
|
|
}).
|
|
Create(session).Error
|
|
}
|
|
|
|
// sqlNullString creates a sql.NullString from a string.
|
|
func sqlNullString(s string) sql.NullString {
|
|
if s == "" {
|
|
return sql.NullString{Valid: false}
|
|
}
|
|
return sql.NullString{String: s, Valid: true}
|
|
}
|
|
|
|
// MaxPaginationLimit is the maximum allowed limit for pagination queries.
|
|
// This protects against resource exhaustion from excessively large requests.
|
|
const MaxPaginationLimit = 1000
|
|
|
|
// ParseLimitParam parses the "limit" query parameter from an HTTP request.
|
|
// Returns defaultLimit if the parameter is missing or invalid.
|
|
// Note: This does NOT enforce a maximum limit. Use ParseLimitParamWithMax for that.
|
|
func ParseLimitParam(r *http.Request, defaultLimit int) int {
|
|
if l := r.URL.Query().Get("limit"); l != "" {
|
|
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 {
|
|
return parsed
|
|
}
|
|
}
|
|
return defaultLimit
|
|
}
|
|
|
|
// ParseLimitParamWithMax parses the "limit" query parameter with a maximum cap.
|
|
// Returns min(parsed, maxLimit) or defaultLimit if missing/invalid.
|
|
// If maxLimit is 0, uses MaxPaginationLimit (1000).
|
|
func ParseLimitParamWithMax(r *http.Request, defaultLimit, maxLimit int) int {
|
|
if maxLimit <= 0 {
|
|
maxLimit = MaxPaginationLimit
|
|
}
|
|
limit := ParseLimitParam(r, defaultLimit)
|
|
if limit > maxLimit {
|
|
return maxLimit
|
|
}
|
|
return limit
|
|
}
|
|
|
|
// ParseOffsetParam parses the "offset" query parameter from an HTTP request.
|
|
// Returns 0 if the parameter is missing or invalid.
|
|
func ParseOffsetParam(r *http.Request) int {
|
|
if o := r.URL.Query().Get("offset"); o != "" {
|
|
if parsed, err := strconv.Atoi(o); err == nil && parsed >= 0 {
|
|
return parsed
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// PaginationParams holds pagination parameters.
|
|
type PaginationParams struct {
|
|
Limit int
|
|
Offset int
|
|
}
|
|
|
|
// ParsePaginationParams parses both limit and offset from an HTTP request.
|
|
func ParsePaginationParams(r *http.Request, defaultLimit int) PaginationParams {
|
|
return PaginationParams{
|
|
Limit: ParseLimitParam(r, defaultLimit),
|
|
Offset: ParseOffsetParam(r),
|
|
}
|
|
}
|