Files
Completed/internal/api/routes.go

258 lines
7.4 KiB
Go

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
})
}