diff --git a/Controllers/PowerItemController.go b/Controllers/PowerItemController.go index c93420d..302f253 100644 --- a/Controllers/PowerItemController.go +++ b/Controllers/PowerItemController.go @@ -1,73 +1,180 @@ package Controllers import ( - "context" "encoding/json" "fmt" "net/http" + "strconv" - "isl-api/sql/powerItem" + "isl-api/Services" + "isl-api/Types" - "github.com/jackc/pgx/v4" + "github.com/julienschmidt/httprouter" ) -func SetPowerItemEndpoints(mux *http.ServeMux, prefix string) { - mux.HandleFunc(fmt.Sprintf("/%s", prefix), PowerItemFunc) - mux.HandleFunc(fmt.Sprintf("/%s/all", prefix), PowerItemAll) +type PowerItemController struct { + powerItemService *Services.PowerItemService } -func PowerItemFunc(w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - fmt.Fprintf(w, "Hello") - // handle a GET - - case http.MethodPost: - var newItem powerItem.AddNewItemWithIDParams - err := json.NewDecoder(r.Body).Decode(&newItem) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - fmt.Fprintf(w, "%+v", newItem) - // handle a POST - - case http.MethodOptions: - w.Header().Set("Allow", "GET, POST, OPTIONS") - w.WriteHeader(http.StatusNoContent) - - default: - w.Header().Set("Allow", "GET, POST, OPTIONS") - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) +func NewPowerItemController(router *httprouter.Router, powerItemService *Services.PowerItemService) *PowerItemController { + controller := &PowerItemController{ + powerItemService: powerItemService, } + controller.setPowerItemEndpoints(router, "/powerItem") + return controller } -func (w http.ResponseWriter, r *http.Request) { - switch r.Method { - case http.MethodGet: - ctx := context.TODO() - conn, err := pgx.Connect(ctx, "postgres://isl:development@localhost:5432") - if err != nil { - panic(err) - } - q := powerItem.NewQuerier(conn) - rows, err := q.GetAllItems(ctx) - if err != nil { - panic(err) - } - data, err := json.Marshal(rows) - if err != nil { - panic(err) - } - w.Header().Add("Content-Type", "application/json") - fmt.Fprintf(w, string(data)) +func (p *PowerItemController) setPowerItemEndpoints(router *httprouter.Router, prefix string) { + router.HandlerFunc("GET", fmt.Sprintf("%v", prefix), p.getAll) + router.HandlerFunc("GET", fmt.Sprintf("%v/asMap", prefix), p.getAllAsMap) + router.HandlerFunc("GET", fmt.Sprintf("%v/byType/:type", prefix), p.getAllByType) + router.HandlerFunc("GET", fmt.Sprintf("%v/byType/:type/asMap", prefix), p.getAllByTypeAsMap) + router.HandlerFunc("POST", fmt.Sprintf("%v", prefix), p.add) + router.HandlerFunc("POST", fmt.Sprintf("%v/multiple", prefix), p.addMultiple) +} - case http.MethodOptions: - w.Header().Set("Allow", "GET, OPTIONS") - w.WriteHeader(http.StatusNoContent) - - default: - w.Header().Set("Allow", "GET, OPTIONS") - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) +func (p *PowerItemController) add(w http.ResponseWriter, r *http.Request) { + var newItem Types.PowerItem + err := json.NewDecoder(r.Body).Decode(&newItem) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return } + + result, err := p.powerItemService.Add(newItem) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + returnValue, err := json.Marshal(result) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, string(returnValue)) +} + +func (p *PowerItemController) addMultiple(w http.ResponseWriter, r *http.Request) { + var itemType int32 = 3 + var newItems map[string]Types.PowerItem + err := json.NewDecoder(r.Body).Decode(&newItems) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + added, errors := p.powerItemService.AddMultipile(newItems, itemType) + if len(errors) != 0 { + http.Error(w, "Could not add item", http.StatusInternalServerError) + for _, err := range errors { + fmt.Println(err.Error()) + } + return + } + + returnValue, err := json.Marshal(added) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, string(returnValue)) +} + +func (p *PowerItemController) getAll(w http.ResponseWriter, r *http.Request) { + result, err := p.powerItemService.GetAll() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + data, err := json.Marshal(result) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, string(data)) +} + +func (p *PowerItemController) getAllAsMap(w http.ResponseWriter, r *http.Request) { + items, err := p.powerItemService.GetAll() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + resultMap := make(map[string]Types.PowerItem) + for _, curItem := range items { + uuid := fmt.Sprintf("%x-%x-%x-%x-%x", curItem.ID.Bytes[0:4], curItem.ID.Bytes[4:6], curItem.ID.Bytes[6:8], curItem.ID.Bytes[8:10], curItem.ID.Bytes[10:16]) + resultMap[uuid] = curItem + } + + data, err := json.Marshal(resultMap) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, string(data)) +} + +func (p *PowerItemController) getAllByType(w http.ResponseWriter, r *http.Request) { + params := httprouter.ParamsFromContext(r.Context()) + typeCode, err := strconv.Atoi(params.ByName("type")) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + result, err := p.powerItemService.GetAllByType(typeCode) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + data, err := json.Marshal(result) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, string(data)) +} + +func (p *PowerItemController) getAllByTypeAsMap(w http.ResponseWriter, r *http.Request) { + params := httprouter.ParamsFromContext(r.Context()) + typeCode, err := strconv.Atoi(params.ByName("type")) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + items, err := p.powerItemService.GetAllByType(typeCode) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + resultMap := make(map[string]Types.PowerItem) + for _, curItem := range items { + uuid := fmt.Sprintf("%x-%x-%x-%x-%x", curItem.ID.Bytes[0:4], curItem.ID.Bytes[4:6], curItem.ID.Bytes[6:8], curItem.ID.Bytes[8:10], curItem.ID.Bytes[10:16]) + resultMap[uuid] = curItem + } + + data, err := json.Marshal(resultMap) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Add("Content-Type", "application/json") + fmt.Fprint(w, string(data)) } diff --git a/Services/PowerItemService.go b/Services/PowerItemService.go index 18ca18e..775871e 100644 --- a/Services/PowerItemService.go +++ b/Services/PowerItemService.go @@ -1,3 +1,93 @@ package Services -func GetAll() +import ( + "context" + "isl-api/Types" + "isl-api/sql/powerItem" + + "github.com/jackc/pgtype" +) + +type PowerItemService struct { + querier *powerItem.DBQuerier + context context.Context +} + +func NewPowerItemService(querier *powerItem.DBQuerier) *PowerItemService { + return &PowerItemService{ + querier: querier, + context: context.Background(), + } +} + +func (p *PowerItemService) GetAll() ([]Types.PowerItem, error) { + rows, err := p.querier.GetAllItems(p.context) + if err != nil { + return []Types.PowerItem{}, err + } + var powerItems []Types.PowerItem + for _, sqlItem := range rows { + powerItems = append(powerItems, Types.FromGetAllItemsRow(sqlItem)) + } + return powerItems, nil +} + +func (p *PowerItemService) GetAllByType(itemType int) ([]Types.PowerItem, error) { + rows, err := p.querier.GetAllByType(p.context, int32(itemType)) + if err != nil { + return []Types.PowerItem{}, err + } + var powerItems []Types.PowerItem + for _, sqlItem := range rows { + powerItems = append(powerItems, Types.FromGetAllItemsByTypeRow(sqlItem)) + } + return powerItems, nil +} + +func (p *PowerItemService) Add(newItem Types.PowerItem) (Types.PowerItem, error) { + sqlItem := powerItem.AddNewItemWithIDParams{ + ID: newItem.ID, + ItemType: newItem.ItemType, + IconUrl: newItem.IconURL, + ItemName: newItem.ItemName, + MinItemPower: newItem.MinItemPower, + MaxItemPower: newItem.MaxItemPower, + Rarity: newItem.Rarity, + Origin: newItem.Origin, + Tooltip: newItem.Tooltip, + IsEventItem: newItem.IsEventItem, + } + row, err := p.querier.AddNewItemWithID(p.context, powerItem.AddNewItemWithIDParams(sqlItem)) + if err != nil { + return Types.PowerItem{}, err + } + return Types.FromAddNewItemWithIDParams(row), nil +} + +func (p *PowerItemService) AddMultipile(newItems map[string]Types.PowerItem, itemType int32) ([]Types.PowerItem, []error) { + var errors []error + var addedItems []Types.PowerItem + for key, value := range newItems { + id := pgtype.UUID{} + id.Set(key) + newItem := powerItem.AddNewItemWithIDParams{ + ID: id, + ItemType: itemType, + IconUrl: value.IconURL, + ItemName: value.ItemName, + MinItemPower: value.MinItemPower, + MaxItemPower: value.MaxItemPower, + Rarity: value.Rarity, + Origin: value.Origin, + Tooltip: value.Tooltip, + IsEventItem: value.IsEventItem, + } + row, err := p.querier.AddNewItemWithID(p.context, powerItem.AddNewItemWithIDParams(newItem)) + if err != nil { + errors = append(errors, err) + continue + } + addedItems = append(addedItems, Types.FromAddNewItemWithIDParams(row)) + } + return addedItems, errors +} diff --git a/Types/PowerItem.go b/Types/PowerItem.go index 6d2347a..f215d77 100644 --- a/Types/PowerItem.go +++ b/Types/PowerItem.go @@ -1,19 +1,65 @@ package Types import ( - "github.com/gofrs/uuid" - uuid "github.com/jackc/pgtype/ext/gofrs-uuid" + "isl-api/sql/powerItem" + + "github.com/jackc/pgtype" ) type PowerItem struct { - ID uuid.UUID `json:"id"` - ItemType int32 `json:"itemType"` - IconUrl string `json:"iconUrl"` - ItemName string `json:"itemName"` - MinItemPower int32 `json:"minItemPower"` - MaxItemPower int32 `json:"maxItemPower"` - Rarity int32 `json:"rarity"` - Origin string `json:"origin"` - Tooltip string `json:"tooltip"` - IsEventItem bool `json:"isEventItem"` + ID pgtype.UUID `json:"id"` + ItemType int32 `json:"itemType"` + IconURL string `json:"iconURL"` + ItemName string `json:"itemName"` + MinItemPower int32 `json:"minItemPower"` + MaxItemPower int32 `json:"maxItemPower"` + Rarity int32 `json:"rarity"` + Origin string `json:"origin"` + Tooltip string `json:"tooltip"` + IsEventItem bool `json:"isEventItem"` +} + +func FromGetAllItemsRow(sqlItem powerItem.GetAllItemsRow) PowerItem { + return PowerItem{ + ID: sqlItem.ID, + ItemType: *sqlItem.Itemtype, + IconURL: *sqlItem.Iconurl, + ItemName: *sqlItem.Itemname, + MinItemPower: *sqlItem.Minitempower, + MaxItemPower: *sqlItem.Maxitempower, + Rarity: *sqlItem.Rarity, + Origin: *sqlItem.Origin, + Tooltip: *sqlItem.Tooltip, + IsEventItem: *sqlItem.Iseventitem, + } +} + +func FromGetAllItemsByTypeRow(sqlItem powerItem.GetAllByTypeRow) PowerItem { + return PowerItem{ + ID: sqlItem.ID, + ItemType: sqlItem.Itemtype, + IconURL: sqlItem.Iconurl, + ItemName: sqlItem.Itemname, + MinItemPower: sqlItem.Minitempower, + MaxItemPower: sqlItem.Maxitempower, + Rarity: sqlItem.Rarity, + Origin: sqlItem.Origin, + Tooltip: *sqlItem.Tooltip, + IsEventItem: *sqlItem.Iseventitem, + } +} + +func FromAddNewItemWithIDParams(sqlItem powerItem.AddNewItemWithIDRow) PowerItem { + return PowerItem{ + ID: sqlItem.ID, + ItemType: sqlItem.Itemtype, + IconURL: sqlItem.Iconurl, + ItemName: sqlItem.Itemname, + MinItemPower: sqlItem.Minitempower, + MaxItemPower: sqlItem.Maxitempower, + Rarity: sqlItem.Rarity, + Origin: sqlItem.Origin, + Tooltip: *sqlItem.Tooltip, + IsEventItem: *sqlItem.Iseventitem, + } } diff --git a/go.mod b/go.mod index 9a05627..2d50edf 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module isl-api go 1.21.6 require ( + github.com/google/uuid v1.6.0 github.com/jackc/pgconn v1.14.1 github.com/jackc/pgtype v1.14.1 github.com/jackc/pgx/v4 v4.18.1 @@ -14,6 +15,7 @@ require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgproto3/v2 v2.3.2 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/julienschmidt/httprouter v1.3.0 // indirect golang.org/x/crypto v0.6.0 // indirect golang.org/x/text v0.7.0 // indirect ) diff --git a/go.sum b/go.sum index a2dac8f..bfef281 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= @@ -64,6 +66,8 @@ github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0f github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= diff --git a/main.go b/main.go index deb6fc6..11d6a70 100644 --- a/main.go +++ b/main.go @@ -1,41 +1,52 @@ package main import ( + "context" + "fmt" "isl-api/Controllers" + "isl-api/Services" + "isl-api/sql/powerItem" "log" "net/http" + + "github.com/jackc/pgx/v4" + "github.com/julienschmidt/httprouter" ) func main() { - mux := http.NewServeMux() - mux.HandleFunc("/", index) - Controllers.SetPowerItemEndpoints(mux, "powerItem") + deps := dependencies{} + deps.initializeDependencies() - err := http.ListenAndServe(":3000", mux) + deps.router.HandlerFunc("GET", "/", index) + + err := http.ListenAndServe(":3000", deps.router) log.Fatal(err) } -func index(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - http.NotFound(w, r) - return - } - - // Common code for all requests can go here... - - switch r.Method { - case http.MethodGet: - // Handle the GET request... - - case http.MethodPost: - // Handle the POST request... - - case http.MethodOptions: - w.Header().Set("Allow", "GET, POST, OPTIONS") - w.WriteHeader(http.StatusNoContent) - - default: - w.Header().Set("Allow", "GET, POST, OPTIONS") - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - } +type dependencies struct { + router *httprouter.Router + postgresConnection *pgx.Conn + context context.Context + powerItemQuerier *powerItem.DBQuerier + powerItemService *Services.PowerItemService + powerItemController *Controllers.PowerItemController +} + +func (d *dependencies) initializeDependencies() error { + var err error + + d.router = httprouter.New() + d.context = context.Background() + d.postgresConnection, err = pgx.Connect(d.context, "postgres://isl:development@localhost:5432/isl") + if err != nil { + return err + } + d.powerItemQuerier = powerItem.NewQuerier(d.postgresConnection) + d.powerItemService = Services.NewPowerItemService(d.powerItemQuerier) + d.powerItemController = Controllers.NewPowerItemController(d.router, d.powerItemService) + return nil +} + +func index(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Index") } diff --git a/sql/powerItem/query.sql b/sql/powerItem/query.sql index 9c61c37..789a1b1 100644 --- a/sql/powerItem/query.sql +++ b/sql/powerItem/query.sql @@ -14,6 +14,24 @@ SELECT FROM powerItem; +-- GetAllByType finds all items of a specific type. +-- name: GetAllByType :many +SELECT + id, + itemType, + iconURL, + itemName, + minItemPower, + maxItemPower, + rarity, + origin, + tooltip, + isEventItem +FROM + powerItem +WHERE + itemType = pggen.arg('itemType'); + -- FindByID finds items by the ID -- name: FindByID :one SELECT