258 lines
7.4 KiB
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
|
|
})
|
|
}
|