package api import ( "context" "net/http" "time" "github.com/danielgtaylor/huma/v2" "github.com/jackc/pgx/v5/pgtype" "completed/internal/db" ) func RegisterRoutes(api huma.API, queries db.Querier) { // Root/Hello Route huma.Register(api, huma.Operation{ OperationID: "get-hello", Method: http.MethodGet, Path: "/hello", Summary: "Say Hello", Description: "Returns a friendly hello message.", }, func(ctx context.Context, input *struct{}) (*struct { Body struct { Message string `json:"message"` } }, error) { resp := &struct { Body struct { Message string `json:"message"` } }{} resp.Body.Message = "Hello from Huma + Echo!" return resp, nil }) registerPlatformRoutes(api, queries) registerGameRoutes(api, queries) } // --- Platform Routes --- // PlatformDTO ensures consistent JSON output type PlatformDTO struct { ID int64 `json:"id"` Name string `json:"name"` } type PlatformInput struct { Name string `json:"name" doc:"Name of the platform"` } func registerPlatformRoutes(api huma.API, queries db.Querier) { huma.Register(api, huma.Operation{ OperationID: "list-platforms", Method: http.MethodGet, Path: "/platforms", Summary: "List Platforms", }, func(ctx context.Context, input *struct{}) (*struct{ Body []PlatformDTO }, error) { platforms, err := queries.ListPlatforms(ctx) if err != nil { return nil, huma.Error500InternalServerError("Database error: " + err.Error()) } dtos := make([]PlatformDTO, len(platforms)) for i, p := range platforms { dtos[i] = PlatformDTO{ID: p.ID, Name: p.Name} } return &struct{ Body []PlatformDTO }{Body: dtos}, nil }) huma.Register(api, huma.Operation{ OperationID: "create-platform", Method: http.MethodPost, Path: "/platforms", Summary: "Create Platform", }, func(ctx context.Context, input *struct{ Body PlatformInput }) (*struct{ Body PlatformDTO }, error) { platform, err := queries.CreatePlatform(ctx, input.Body.Name) if err != nil { return nil, huma.Error500InternalServerError("Database error: " + err.Error()) } return &struct{ Body PlatformDTO }{Body: PlatformDTO{ID: platform.ID, Name: platform.Name}}, nil }) huma.Register(api, huma.Operation{ OperationID: "update-platform", Method: http.MethodPut, Path: "/platforms/{id}", Summary: "Update Platform", }, func(ctx context.Context, input *struct { ID int64 `path:"id"` Body PlatformInput }) (*struct{ Body PlatformDTO }, error) { platform, err := queries.UpdatePlatform(ctx, db.UpdatePlatformParams{ ID: input.ID, Name: input.Body.Name, }) if err != nil { return nil, huma.Error500InternalServerError("Database error: " + err.Error()) } return &struct{ Body PlatformDTO }{Body: PlatformDTO{ID: platform.ID, Name: platform.Name}}, nil }) huma.Register(api, huma.Operation{ OperationID: "delete-platform", Method: http.MethodDelete, Path: "/platforms/{id}", Summary: "Delete Platform", }, func(ctx context.Context, input *struct { ID int64 `path:"id"` }) (*struct{}, error) { err := queries.DeletePlatform(ctx, input.ID) if err != nil { return nil, huma.Error500InternalServerError("Database error: " + err.Error()) } return nil, nil }) } // --- Game Routes --- // GameDTO for API communication (Clean JSON) type GameDTO struct { ID int64 `json:"id"` Name string `json:"name"` PlatformID int64 `json:"platform_id"` Score int32 `json:"score"` ReleaseYear string `json:"release_year" format:"date" doc:"YYYY-MM-DD"` Finished string `json:"finished,omitempty" format:"date" doc:"YYYY-MM-DD (optional)"` } type GameInput struct { Name string `json:"name"` PlatformID int64 `json:"platform_id"` Score int32 `json:"score"` ReleaseYear string `json:"release_year" format:"date" doc:"YYYY-MM-DD"` Finished string `json:"finished,omitempty" format:"date" doc:"YYYY-MM-DD (optional)"` } // Helper to convert DB Game to DTO func toGameDTO(g db.Game) GameDTO { dto := GameDTO{ ID: g.ID, Name: g.Name, PlatformID: g.PlatformID, Score: g.Score, ReleaseYear: g.ReleaseYear.Time.Format("2006-01-02"), } if g.Finished.Valid { dto.Finished = g.Finished.Time.Format("2006-01-02") } return dto } // Helper to parse date string to pgtype.Date func parseDate(s string) (pgtype.Date, error) { if s == "" { return pgtype.Date{}, nil } t, err := time.Parse("2006-01-02", s) if err != nil { return pgtype.Date{}, err } return pgtype.Date{Time: t, Valid: true}, nil } func registerGameRoutes(api huma.API, queries db.Querier) { huma.Register(api, huma.Operation{ OperationID: "list-games", Method: http.MethodGet, Path: "/games", Summary: "List Games", }, func(ctx context.Context, input *struct{}) (*struct{ Body []GameDTO }, error) { games, err := queries.ListGames(ctx) if err != nil { return nil, huma.Error500InternalServerError("Database error: " + err.Error()) } dtos := make([]GameDTO, len(games)) for i, g := range games { dtos[i] = toGameDTO(g) } return &struct{ Body []GameDTO }{Body: dtos}, nil }) huma.Register(api, huma.Operation{ OperationID: "create-game", Method: http.MethodPost, Path: "/games", Summary: "Create Game", }, func(ctx context.Context, input *struct{ Body GameInput }) (*struct{ Body GameDTO }, error) { ry, err := parseDate(input.Body.ReleaseYear) if err != nil { return nil, huma.Error400BadRequest("Invalid release_year format (expected YYYY-MM-DD)") } fin, err := parseDate(input.Body.Finished) if err != nil { return nil, huma.Error400BadRequest("Invalid finished format (expected YYYY-MM-DD)") } game, err := queries.CreateGame(ctx, db.CreateGameParams{ Name: input.Body.Name, PlatformID: input.Body.PlatformID, Score: input.Body.Score, ReleaseYear: ry, Finished: fin, }) if err != nil { return nil, huma.Error500InternalServerError("Database error: " + err.Error()) } return &struct{ Body GameDTO }{Body: toGameDTO(game)}, nil }) huma.Register(api, huma.Operation{ OperationID: "update-game", Method: http.MethodPut, Path: "/games/{id}", Summary: "Update Game", }, func(ctx context.Context, input *struct { ID int64 `path:"id"` Body GameInput }) (*struct{ Body GameDTO }, error) { ry, err := parseDate(input.Body.ReleaseYear) if err != nil { return nil, huma.Error400BadRequest("Invalid release_year format (expected YYYY-MM-DD)") } fin, err := parseDate(input.Body.Finished) if err != nil { return nil, huma.Error400BadRequest("Invalid finished format (expected YYYY-MM-DD)") } game, err := queries.UpdateGame(ctx, db.UpdateGameParams{ ID: input.ID, Name: input.Body.Name, PlatformID: input.Body.PlatformID, Score: input.Body.Score, ReleaseYear: ry, Finished: fin, }) if err != nil { return nil, huma.Error500InternalServerError("Database error: " + err.Error()) } return &struct{ Body GameDTO }{Body: toGameDTO(game)}, nil }) huma.Register(api, huma.Operation{ OperationID: "delete-game", Method: http.MethodDelete, Path: "/games/{id}", Summary: "Delete Game", }, func(ctx context.Context, input *struct { ID int64 `path:"id"` }) (*struct{}, error) { err := queries.DeleteGame(ctx, input.ID) if err != nil { return nil, huma.Error500InternalServerError("Database error: " + err.Error()) } return nil, nil }) }