├── .gitignore ├── Dockerfile ├── README.md ├── api ├── cardDTO.go ├── cardDTO_test.go ├── closedDeckDTO.go ├── closedDeckDTO_test.go ├── handlers.go ├── init.go ├── logger.go ├── openDeckDTO.go └── openDeckDTO_test.go ├── docs ├── docs.go ├── swagger.json └── swagger.yaml ├── domain ├── card.go ├── deck.go ├── deck_test.go ├── init.go ├── rank.go └── shape.go ├── go.mod ├── go.sum ├── main.go └── storage └── storage.go /.gitignore: -------------------------------------------------------------------------------- 1 | debug.log 2 | .devcontainer/ 3 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.20-bullseye 2 | RUN mkdir /app 3 | COPY . /app 4 | WORKDIR /app 5 | RUN go build -o server . 6 | EXPOSE 8080 7 | CMD [ "/app/server" ] 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Building the application 2 | Running the code is as simple as 3 | ``` 4 | go run main.go 5 | ``` 6 | 7 | ### Using Docker 8 | Alternatively if you want to leverage pre-installed environment you can take advantage of supplied Dockerfile. In such a case you should perform 9 | ``` 10 | docker build -t . 11 | ``` 12 | and then 13 | ``` 14 | docker run -it --rm -p 8080:8080 15 | ``` 16 | 17 | ## Geging to know the application 18 | The software leverages OpenAPI so you can access the documentation at http://localhost:8080/swagger/index.html -------------------------------------------------------------------------------- /api/cardDTO.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "errors" 5 | "toggl-deck-management-api/domain" 6 | ) 7 | 8 | type CardDTO struct { 9 | Rank string `json:"value"` 10 | Shape string `json:"suit"` 11 | Code string `json:"code"` 12 | } 13 | 14 | type StringRepresentation struct { 15 | code string 16 | fullname string 17 | } 18 | 19 | func createCardDTO(card domain.Card) CardDTO { 20 | return CardDTO{ 21 | Rank: GetRankFullname(card.Rank), 22 | Shape: GetShapeFullname(card.Shape), 23 | Code: GetCardStringCode(card), 24 | } 25 | } 26 | 27 | var shapeToStringRepresentationMap map[domain.Shape]StringRepresentation 28 | var letterToShapeMap map[string]domain.Shape 29 | var rankToStringRepresentationMap map[domain.Rank]StringRepresentation 30 | var letterToRankMap map[string]domain.Rank 31 | 32 | func GetCardStringCode(card domain.Card) string { 33 | return rankToStringRepresentationMap[card.Rank].code + shapeToStringRepresentationMap[card.Shape].code 34 | } 35 | 36 | func parseCardStringCode(code string) (domain.Card, error) { 37 | if len(code) < 2 || len(code) > 3 { 38 | return domain.Card{}, errors.New("ParseCardStringCode. Invalid card code") 39 | } 40 | rankCode := code[:len(code)-1] 41 | shapeCode := code[len(code)-1:] 42 | rank, rankFound := letterToRankMap[rankCode] 43 | shape, shapeFound := letterToShapeMap[shapeCode] 44 | if !rankFound || !shapeFound { 45 | return domain.Card{}, errors.New("ParseCardStringCode. Invalid card code") 46 | } 47 | return domain.CreateCard(rank, shape), nil 48 | } 49 | 50 | func GetShapeFullname(shape domain.Shape) string { 51 | return shapeToStringRepresentationMap[shape].fullname 52 | } 53 | 54 | func GetRankFullname(rank domain.Rank) string { 55 | return rankToStringRepresentationMap[rank].fullname 56 | } 57 | -------------------------------------------------------------------------------- /api/cardDTO_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | "toggl-deck-management-api/domain" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | type CreateCardDTODataItem struct { 11 | input domain.Card 12 | actual CardDTO 13 | } 14 | 15 | var CreateCardDTOData = []CreateCardDTODataItem{ 16 | {domain.CreateCard(domain.Ace, domain.Spades), CardDTO{"ACE", "SPADES", "AS"}}, 17 | {domain.CreateCard(domain.Two, domain.Spades), CardDTO{"2", "SPADES", "2S"}}, 18 | {domain.CreateCard(domain.Three, domain.Spades), CardDTO{"3", "SPADES", "3S"}}, 19 | {domain.CreateCard(domain.Four, domain.Spades), CardDTO{"4", "SPADES", "4S"}}, 20 | {domain.CreateCard(domain.Five, domain.Spades), CardDTO{"5", "SPADES", "5S"}}, 21 | {domain.CreateCard(domain.Six, domain.Spades), CardDTO{"6", "SPADES", "6S"}}, 22 | {domain.CreateCard(domain.Seven, domain.Spades), CardDTO{"7", "SPADES", "7S"}}, 23 | {domain.CreateCard(domain.Eight, domain.Spades), CardDTO{"8", "SPADES", "8S"}}, 24 | {domain.CreateCard(domain.Nine, domain.Spades), CardDTO{"9", "SPADES", "9S"}}, 25 | {domain.CreateCard(domain.Ten, domain.Spades), CardDTO{"10", "SPADES", "10S"}}, 26 | {domain.CreateCard(domain.Jack, domain.Spades), CardDTO{"JACK", "SPADES", "JS"}}, 27 | {domain.CreateCard(domain.Queen, domain.Spades), CardDTO{"QUEEN", "SPADES", "QS"}}, 28 | {domain.CreateCard(domain.King, domain.Spades), CardDTO{"KING", "SPADES", "KS"}}, 29 | {domain.CreateCard(domain.Ace, domain.Hearts), CardDTO{"ACE", "HEARTS", "AH"}}, 30 | {domain.CreateCard(domain.Two, domain.Hearts), CardDTO{"2", "HEARTS", "2H"}}, 31 | {domain.CreateCard(domain.Three, domain.Hearts), CardDTO{"3", "HEARTS", "3H"}}, 32 | {domain.CreateCard(domain.Four, domain.Hearts), CardDTO{"4", "HEARTS", "4H"}}, 33 | {domain.CreateCard(domain.Five, domain.Hearts), CardDTO{"5", "HEARTS", "5H"}}, 34 | {domain.CreateCard(domain.Six, domain.Hearts), CardDTO{"6", "HEARTS", "6H"}}, 35 | {domain.CreateCard(domain.Seven, domain.Hearts), CardDTO{"7", "HEARTS", "7H"}}, 36 | {domain.CreateCard(domain.Eight, domain.Hearts), CardDTO{"8", "HEARTS", "8H"}}, 37 | {domain.CreateCard(domain.Nine, domain.Hearts), CardDTO{"9", "HEARTS", "9H"}}, 38 | {domain.CreateCard(domain.Ten, domain.Hearts), CardDTO{"10", "HEARTS", "10H"}}, 39 | {domain.CreateCard(domain.Jack, domain.Hearts), CardDTO{"JACK", "HEARTS", "JH"}}, 40 | {domain.CreateCard(domain.Queen, domain.Hearts), CardDTO{"QUEEN", "HEARTS", "QH"}}, 41 | {domain.CreateCard(domain.King, domain.Hearts), CardDTO{"KING", "HEARTS", "KH"}}, 42 | {domain.CreateCard(domain.Ace, domain.Clubs), CardDTO{"ACE", "CLUBS", "AC"}}, 43 | {domain.CreateCard(domain.Two, domain.Clubs), CardDTO{"2", "CLUBS", "2C"}}, 44 | {domain.CreateCard(domain.Three, domain.Clubs), CardDTO{"3", "CLUBS", "3C"}}, 45 | {domain.CreateCard(domain.Four, domain.Clubs), CardDTO{"4", "CLUBS", "4C"}}, 46 | {domain.CreateCard(domain.Five, domain.Clubs), CardDTO{"5", "CLUBS", "5C"}}, 47 | {domain.CreateCard(domain.Six, domain.Clubs), CardDTO{"6", "CLUBS", "6C"}}, 48 | {domain.CreateCard(domain.Seven, domain.Clubs), CardDTO{"7", "CLUBS", "7C"}}, 49 | {domain.CreateCard(domain.Eight, domain.Clubs), CardDTO{"8", "CLUBS", "8C"}}, 50 | {domain.CreateCard(domain.Nine, domain.Clubs), CardDTO{"9", "CLUBS", "9C"}}, 51 | {domain.CreateCard(domain.Ten, domain.Clubs), CardDTO{"10", "CLUBS", "10C"}}, 52 | {domain.CreateCard(domain.Jack, domain.Clubs), CardDTO{"JACK", "CLUBS", "JC"}}, 53 | {domain.CreateCard(domain.Queen, domain.Clubs), CardDTO{"QUEEN", "CLUBS", "QC"}}, 54 | {domain.CreateCard(domain.King, domain.Clubs), CardDTO{"KING", "CLUBS", "KC"}}, 55 | {domain.CreateCard(domain.Ace, domain.Diamonds), CardDTO{"ACE", "DIAMONDS", "AD"}}, 56 | {domain.CreateCard(domain.Two, domain.Diamonds), CardDTO{"2", "DIAMONDS", "2D"}}, 57 | {domain.CreateCard(domain.Three, domain.Diamonds), CardDTO{"3", "DIAMONDS", "3D"}}, 58 | {domain.CreateCard(domain.Four, domain.Diamonds), CardDTO{"4", "DIAMONDS", "4D"}}, 59 | {domain.CreateCard(domain.Five, domain.Diamonds), CardDTO{"5", "DIAMONDS", "5D"}}, 60 | {domain.CreateCard(domain.Six, domain.Diamonds), CardDTO{"6", "DIAMONDS", "6D"}}, 61 | {domain.CreateCard(domain.Seven, domain.Diamonds), CardDTO{"7", "DIAMONDS", "7D"}}, 62 | {domain.CreateCard(domain.Eight, domain.Diamonds), CardDTO{"8", "DIAMONDS", "8D"}}, 63 | {domain.CreateCard(domain.Nine, domain.Diamonds), CardDTO{"9", "DIAMONDS", "9D"}}, 64 | {domain.CreateCard(domain.Ten, domain.Diamonds), CardDTO{"10", "DIAMONDS", "10D"}}, 65 | {domain.CreateCard(domain.Jack, domain.Diamonds), CardDTO{"JACK", "DIAMONDS", "JD"}}, 66 | {domain.CreateCard(domain.Queen, domain.Diamonds), CardDTO{"QUEEN", "DIAMONDS", "QD"}}, 67 | {domain.CreateCard(domain.King, domain.Diamonds), CardDTO{"KING", "DIAMONDS", "KD"}}, 68 | } 69 | 70 | func TestCreateCardDTO(t *testing.T) { 71 | for _, item := range CreateCardDTOData { 72 | actual := createCardDTO(item.input) 73 | assert.Equal(t, item.actual, actual) 74 | } 75 | } 76 | 77 | type TestParseCardStringCodeDataItem struct { 78 | code string 79 | isError bool 80 | card domain.Card 81 | } 82 | 83 | var TestParseCardStringCodeData = []TestParseCardStringCodeDataItem{ 84 | {"13F", true, domain.Card{}}, 85 | {"9F", true, domain.Card{}}, 86 | {"13S", true, domain.Card{}}, 87 | {"8C", false, domain.CreateCard(domain.Eight, domain.Clubs)}, 88 | {"10S", false, domain.CreateCard(domain.Ten, domain.Spades)}, 89 | } 90 | 91 | func TestParseCardStringCode(t *testing.T) { 92 | for _, data := range TestParseCardStringCodeData { 93 | actual, err := parseCardStringCode(data.code) 94 | if data.isError && err == nil { 95 | t.Log("Expected to fail for " + data.code) 96 | t.Fail() 97 | } else if !data.isError && err != nil { 98 | t.Log("Expected to succeed for " + data.code) 99 | t.Fail() 100 | } 101 | assert.Equal(t, data.card, actual) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /api/closedDeckDTO.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "toggl-deck-management-api/domain" 5 | 6 | "github.com/google/uuid" 7 | ) 8 | 9 | type ClosedDeckDTO struct { 10 | DeckId uuid.UUID `json:"deck_id"` 11 | Shuffled bool `json:"shuffled"` 12 | Remaining uint8 `json:"remaining"` 13 | } 14 | 15 | func createClosedDeckDTO(deck domain.Deck) ClosedDeckDTO { 16 | return ClosedDeckDTO{ 17 | DeckId: deck.DeckId, 18 | Shuffled: deck.Shuffled, 19 | Remaining: domain.CountRemainingCards(deck), 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /api/closedDeckDTO_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | "toggl-deck-management-api/domain" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | type TestCreateClosedDeckDTODataItem struct { 11 | shuffled bool 12 | cards []domain.Card 13 | remaining uint8 14 | } 15 | 16 | var TestCreateClosedDeckDTOData = []TestCreateClosedDeckDTODataItem{ 17 | {false, []domain.Card{}, 52}, 18 | {true, []domain.Card{}, 52}, 19 | {false, []domain.Card{domain.CreateCard(domain.Ace, domain.Clubs), domain.CreateCard(domain.Five, domain.Diamonds)}, 2}, 20 | } 21 | 22 | func TestCreateClosedDeckDTO(t *testing.T) { 23 | for _, item := range TestCreateClosedDeckDTOData { 24 | deck := domain.CreateDeck(item.shuffled, item.cards...) 25 | dto := createClosedDeckDTO(deck) 26 | assert.Equal(t, item.shuffled, dto.Shuffled) 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /api/handlers.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "strings" 5 | "toggl-deck-management-api/domain" 6 | "toggl-deck-management-api/storage" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/google/uuid" 10 | ) 11 | 12 | type CreateDeckArgs struct { 13 | Shuffled bool `form:"shuffled"` 14 | Cards string `form:"cards"` 15 | } 16 | 17 | type OpenDeckArgs struct { 18 | DeckId string `form:"deck_id"` 19 | } 20 | 21 | type DrawCardsArgs struct { 22 | DeckId string `form:"deck_id"` 23 | Count uint8 `form:"count"` 24 | } 25 | 26 | // CreateDeckHandler godoc 27 | // @Summary Creates new deck. 28 | // @Description Creates deck that can be either shuffled or unshuffled. It can accept the list of exact cards which can be shuffled or unshuffled as well. In case no cards provided it returns a deck with 52 cards. 29 | // @Accept */* 30 | // @Produce json 31 | // @Param shuffled query bool true "indicates whether deck is shuffled" 32 | // @Param cards query array false "array of card codes i.e. 8C,AS,7D" 33 | // @Success 200 {object} ClosedDeckDTO 34 | // @Router /create-deck [post] 35 | func CreateDeckHandler(c *gin.Context) { 36 | var args CreateDeckArgs 37 | if c.ShouldBind(&args) == nil { 38 | var domainCards []domain.Card 39 | if args.Cards != "" { 40 | for _, card := range strings.Split(args.Cards, ",") { 41 | domainCard, err := parseCardStringCode(card) 42 | if err == nil { 43 | domainCards = append(domainCards, domainCard) 44 | } else { 45 | c.String(400, "Invalid request. Invalid card code "+card) 46 | return 47 | } 48 | } 49 | } 50 | deck := domain.CreateDeck(args.Shuffled, domainCards...) 51 | storage.Add(deck) 52 | dto := createClosedDeckDTO(deck) 53 | c.JSON(200, dto) 54 | return 55 | } else { 56 | c.String(400, "Ivalid request. Expecting query of type ?shuffled=&cards=,,...") 57 | return 58 | } 59 | } 60 | 61 | // OpenDeckHandler godoc 62 | // @Summary Opens deck. 63 | // @Description Returns a deck with all of its cars revealed. 64 | // @Accept */* 65 | // @Produce json 66 | // @Param deck_id query string true "id of the deck" 67 | // @Success 200 {object} OpenDeckDTO 68 | // @Router /open-deck [get] 69 | func OpenDeckHandler(c *gin.Context) { 70 | var args OpenDeckArgs 71 | if c.ShouldBind(&args) == nil { 72 | deckId, err := uuid.Parse(args.DeckId) 73 | if err != nil { 74 | c.String(400, "Bad Request. Expecing request in format ?deck_id=") 75 | return 76 | } 77 | deck, found := storage.Get(deckId) 78 | if !found { 79 | c.String(400, "Bad Request. Deck with given id not found") 80 | return 81 | } 82 | dto := createOpenDeckDTO(*deck) 83 | c.JSON(200, dto) 84 | return 85 | } else { 86 | c.String(400, "Bad Request. Expecing request in format ?deck_id=") 87 | return 88 | } 89 | } 90 | 91 | // DrawCardsHandler godoc 92 | // @Summary Draws cards from a deck. 93 | // @Description Removes given number of cards from a deck and returns them as a response. 94 | // @Accept */* 95 | // @Produce json 96 | // @Param deck_id query string true "id of the deck" 97 | // @Param count query uint8 true "number of cards to draw" 98 | // @Success 200 {object} []CardDTO 99 | // @Router /draw-cards [put] 100 | func DrawCardsHandler(c *gin.Context) { 101 | var args DrawCardsArgs 102 | if c.ShouldBind(&args) == nil { 103 | deckId, err := uuid.Parse(args.DeckId) 104 | if err != nil { 105 | c.String(400, "Bad Request. Expecing request in format ?deck_id=") 106 | return 107 | } 108 | deck, found := storage.Get(deckId) 109 | if !found { 110 | c.String(400, "Bad Request. Failed to find deck by given id. Expecting request in format ?deck_id=&count=") 111 | return 112 | } 113 | cards, err := domain.DrawCards(deck, args.Count) 114 | if err != nil { 115 | c.String(400, "Bad Request. Failed to draw cards from the deck") 116 | return 117 | } 118 | var dto []CardDTO 119 | for _, card := range cards { 120 | dto = append(dto, createCardDTO(card)) 121 | } 122 | storage.Add(*deck) 123 | c.JSON(200, dto) 124 | return 125 | } else { 126 | c.String(400, "Bad Request. Expecting request in format ?deck_id=&count=") 127 | return 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /api/init.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "strconv" 5 | "toggl-deck-management-api/domain" 6 | ) 7 | 8 | func init() { 9 | shapeToStringRepresentationMap = make(map[domain.Shape]StringRepresentation) 10 | shapeToStringRepresentationMap[domain.Spades] = StringRepresentation{code: "S", fullname: "SPADES"} 11 | shapeToStringRepresentationMap[domain.Clubs] = StringRepresentation{code: "C", fullname: "CLUBS"} 12 | shapeToStringRepresentationMap[domain.Diamonds] = StringRepresentation{code: "D", fullname: "DIAMONDS"} 13 | shapeToStringRepresentationMap[domain.Hearts] = StringRepresentation{code: "H", fullname: "HEARTS"} 14 | rankToStringRepresentationMap = make(map[domain.Rank]StringRepresentation) 15 | rankToStringRepresentationMap[domain.Ace] = StringRepresentation{code: "A", fullname: "ACE"} 16 | for i := 1; i <= 9; i++ { 17 | rankToStringRepresentationMap[domain.Rank(i)] = StringRepresentation{code: strconv.Itoa(i + 1), fullname: strconv.Itoa(i + 1)} 18 | } 19 | rankToStringRepresentationMap[domain.Jack] = StringRepresentation{code: "J", fullname: "JACK"} 20 | rankToStringRepresentationMap[domain.Queen] = StringRepresentation{code: "Q", fullname: "QUEEN"} 21 | rankToStringRepresentationMap[domain.King] = StringRepresentation{code: "K", fullname: "KING"} 22 | letterToShapeMap = make(map[string]domain.Shape) 23 | letterToShapeMap["S"] = domain.Spades 24 | letterToShapeMap["C"] = domain.Clubs 25 | letterToShapeMap["D"] = domain.Diamonds 26 | letterToShapeMap["H"] = domain.Hearts 27 | letterToRankMap = make(map[string]domain.Rank) 28 | letterToRankMap["A"] = domain.Ace 29 | for i := 1; i <= 9; i++ { 30 | letterToRankMap[strconv.Itoa(i+1)] = domain.Rank(i) 31 | } 32 | letterToRankMap["J"] = domain.Jack 33 | letterToRankMap["Q"] = domain.Queen 34 | letterToRankMap["K"] = domain.King 35 | } 36 | -------------------------------------------------------------------------------- /api/logger.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/gin-gonic/gin" 7 | "go.uber.org/zap" 8 | ) 9 | 10 | func StructuredLogger() gin.HandlerFunc { 11 | return func(c *gin.Context) { 12 | logger, _ := zap.NewProduction() 13 | defer logger.Sync() 14 | sugar := logger.Sugar() 15 | start := time.Now() 16 | c.Next() 17 | stop := time.Now() 18 | latency := stop.Sub(start) 19 | if c.Writer.Status() >= 500 { 20 | sugar.Errorw("error", c.Errors.ByType(gin.ErrorTypePrivate).String(), 21 | "IP", c.ClientIP(), 22 | "method", c.Request.Method, 23 | "status", c.Writer.Status(), 24 | "size", c.Writer.Size(), 25 | "path", c.Request.URL.Path, 26 | "latency", latency) 27 | } else { 28 | sugar.Infow("request", 29 | "IP", c.ClientIP(), 30 | "method", c.Request.Method, 31 | "status", c.Writer.Status(), 32 | "size", c.Writer.Size(), 33 | "path", c.Request.URL.Path, 34 | "latency", latency) 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /api/openDeckDTO.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "toggl-deck-management-api/domain" 5 | 6 | "github.com/google/uuid" 7 | ) 8 | 9 | type OpenDeckDTO struct { 10 | DeckId uuid.UUID `json:"deck_id"` 11 | Shuffled bool `json:"shuffled"` 12 | Remaining uint8 `json:"remaining"` 13 | Cards []CardDTO `json:"cards"` 14 | } 15 | 16 | func createOpenDeckDTO(deck domain.Deck) OpenDeckDTO { 17 | var cards []CardDTO 18 | for _, domainCard := range deck.Cards { 19 | cards = append(cards, createCardDTO(domainCard)) 20 | } 21 | return OpenDeckDTO{ 22 | DeckId: deck.DeckId, 23 | Shuffled: deck.Shuffled, 24 | Remaining: domain.CountRemainingCards(deck), 25 | Cards: cards, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /api/openDeckDTO_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "testing" 5 | "toggl-deck-management-api/domain" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCreateOpenDeckDTO(t *testing.T) { 11 | deck := domain.CreateDeck(false, domain.CreateCard(domain.Ace, domain.Clubs), domain.CreateCard(domain.Four, domain.Clubs)) 12 | actual := createOpenDeckDTO(deck) 13 | assert.Equal(t, uint8(2), actual.Remaining) 14 | assert.False(t, actual.Shuffled) 15 | expectedCard0 := CardDTO{ 16 | Rank: "ACE", 17 | Shape: "CLUBS", 18 | Code: "AC", 19 | } 20 | assert.Equal(t, expectedCard0, actual.Cards[0]) 21 | expectedCard1 := CardDTO{ 22 | Rank: "4", 23 | Shape: "CLUBS", 24 | Code: "4C", 25 | } 26 | assert.Equal(t, expectedCard1, actual.Cards[1]) 27 | } 28 | -------------------------------------------------------------------------------- /docs/docs.go: -------------------------------------------------------------------------------- 1 | // Package docs GENERATED BY SWAG; DO NOT EDIT 2 | // This file was generated by swaggo/swag 3 | package docs 4 | 5 | import "github.com/swaggo/swag" 6 | 7 | const docTemplate = `{ 8 | "schemes": {{ marshal .Schemes }}, 9 | "swagger": "2.0", 10 | "info": { 11 | "description": "{{escape .Description}}", 12 | "title": "{{.Title}}", 13 | "termsOfService": "http://swagger.io/terms/", 14 | "contact": { 15 | "name": "API Support", 16 | "url": "http://www.swagger.io/support", 17 | "email": "support@swagger.io" 18 | }, 19 | "license": { 20 | "name": "Apache 2.0", 21 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 22 | }, 23 | "version": "{{.Version}}" 24 | }, 25 | "host": "{{.Host}}", 26 | "basePath": "{{.BasePath}}", 27 | "paths": { 28 | "/create-deck": { 29 | "post": { 30 | "description": "Creates deck that can be either shuffled or unshuffled. It can accept the list of exact cards which can be shuffled or unshuffled as well. In case no cards provided it returns a deck with 52 cards.", 31 | "consumes": [ 32 | "*/*" 33 | ], 34 | "produces": [ 35 | "application/json" 36 | ], 37 | "summary": "Creates new deck.", 38 | "parameters": [ 39 | { 40 | "type": "boolean", 41 | "description": "indicates whether deck is shuffled", 42 | "name": "shuffled", 43 | "in": "query", 44 | "required": true 45 | }, 46 | { 47 | "type": "array", 48 | "description": "array of card codes i.e. 8C,AS,7D", 49 | "name": "cards", 50 | "in": "query" 51 | } 52 | ], 53 | "responses": { 54 | "200": { 55 | "description": "OK", 56 | "schema": { 57 | "$ref": "#/definitions/api.ClosedDeckDTO" 58 | } 59 | } 60 | } 61 | } 62 | }, 63 | "/draw-cards": { 64 | "put": { 65 | "description": "Removes given number of cards from a deck and returns them as a response.", 66 | "consumes": [ 67 | "*/*" 68 | ], 69 | "produces": [ 70 | "application/json" 71 | ], 72 | "summary": "Draws cards from a deck.", 73 | "parameters": [ 74 | { 75 | "type": "string", 76 | "description": "id of the deck", 77 | "name": "deck_id", 78 | "in": "query", 79 | "required": true 80 | }, 81 | { 82 | "type": "integer", 83 | "description": "number of cards to draw", 84 | "name": "count", 85 | "in": "query", 86 | "required": true 87 | } 88 | ], 89 | "responses": { 90 | "200": { 91 | "description": "OK", 92 | "schema": { 93 | "type": "array", 94 | "items": { 95 | "$ref": "#/definitions/api.CardDTO" 96 | } 97 | } 98 | } 99 | } 100 | } 101 | }, 102 | "/open-deck": { 103 | "get": { 104 | "description": "Returns a deck with all of its cars revealed.", 105 | "consumes": [ 106 | "*/*" 107 | ], 108 | "produces": [ 109 | "application/json" 110 | ], 111 | "summary": "Opens deck.", 112 | "parameters": [ 113 | { 114 | "type": "string", 115 | "description": "id of the deck", 116 | "name": "deck_id", 117 | "in": "query", 118 | "required": true 119 | } 120 | ], 121 | "responses": { 122 | "200": { 123 | "description": "OK", 124 | "schema": { 125 | "$ref": "#/definitions/api.OpenDeckDTO" 126 | } 127 | } 128 | } 129 | } 130 | } 131 | }, 132 | "definitions": { 133 | "api.CardDTO": { 134 | "type": "object", 135 | "properties": { 136 | "code": { 137 | "type": "string" 138 | }, 139 | "suit": { 140 | "type": "string" 141 | }, 142 | "value": { 143 | "type": "string" 144 | } 145 | } 146 | }, 147 | "api.ClosedDeckDTO": { 148 | "type": "object", 149 | "properties": { 150 | "deck_id": { 151 | "type": "string" 152 | }, 153 | "remaining": { 154 | "type": "integer" 155 | }, 156 | "shuffled": { 157 | "type": "boolean" 158 | } 159 | } 160 | }, 161 | "api.OpenDeckDTO": { 162 | "type": "object", 163 | "properties": { 164 | "cards": { 165 | "type": "array", 166 | "items": { 167 | "$ref": "#/definitions/api.CardDTO" 168 | } 169 | }, 170 | "deck_id": { 171 | "type": "string" 172 | }, 173 | "remaining": { 174 | "type": "integer" 175 | }, 176 | "shuffled": { 177 | "type": "boolean" 178 | } 179 | } 180 | } 181 | } 182 | }` 183 | 184 | // SwaggerInfo holds exported Swagger Info so clients can modify it 185 | var SwaggerInfo = &swag.Spec{ 186 | Version: "0.1", 187 | Host: "localhost:8080", 188 | BasePath: "/", 189 | Schemes: []string{"http"}, 190 | Title: "Deck Management API", 191 | Description: "This is a sample server server.", 192 | InfoInstanceName: "swagger", 193 | SwaggerTemplate: docTemplate, 194 | } 195 | 196 | func init() { 197 | swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) 198 | } 199 | -------------------------------------------------------------------------------- /docs/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemes": [ 3 | "http" 4 | ], 5 | "swagger": "2.0", 6 | "info": { 7 | "description": "This is a sample server server.", 8 | "title": "Deck Management API", 9 | "termsOfService": "http://swagger.io/terms/", 10 | "contact": { 11 | "name": "API Support", 12 | "url": "http://www.swagger.io/support", 13 | "email": "support@swagger.io" 14 | }, 15 | "license": { 16 | "name": "Apache 2.0", 17 | "url": "http://www.apache.org/licenses/LICENSE-2.0.html" 18 | }, 19 | "version": "0.1" 20 | }, 21 | "host": "localhost:8080", 22 | "basePath": "/", 23 | "paths": { 24 | "/create-deck": { 25 | "post": { 26 | "description": "Creates deck that can be either shuffled or unshuffled. It can accept the list of exact cards which can be shuffled or unshuffled as well. In case no cards provided it returns a deck with 52 cards.", 27 | "consumes": [ 28 | "*/*" 29 | ], 30 | "produces": [ 31 | "application/json" 32 | ], 33 | "summary": "Creates new deck.", 34 | "parameters": [ 35 | { 36 | "type": "boolean", 37 | "description": "indicates whether deck is shuffled", 38 | "name": "shuffled", 39 | "in": "query", 40 | "required": true 41 | }, 42 | { 43 | "type": "array", 44 | "description": "array of card codes i.e. 8C,AS,7D", 45 | "name": "cards", 46 | "in": "query" 47 | } 48 | ], 49 | "responses": { 50 | "200": { 51 | "description": "OK", 52 | "schema": { 53 | "$ref": "#/definitions/api.ClosedDeckDTO" 54 | } 55 | } 56 | } 57 | } 58 | }, 59 | "/draw-cards": { 60 | "put": { 61 | "description": "Removes given number of cards from a deck and returns them as a response.", 62 | "consumes": [ 63 | "*/*" 64 | ], 65 | "produces": [ 66 | "application/json" 67 | ], 68 | "summary": "Draws cards from a deck.", 69 | "parameters": [ 70 | { 71 | "type": "string", 72 | "description": "id of the deck", 73 | "name": "deck_id", 74 | "in": "query", 75 | "required": true 76 | }, 77 | { 78 | "type": "integer", 79 | "description": "number of cards to draw", 80 | "name": "count", 81 | "in": "query", 82 | "required": true 83 | } 84 | ], 85 | "responses": { 86 | "200": { 87 | "description": "OK", 88 | "schema": { 89 | "type": "array", 90 | "items": { 91 | "$ref": "#/definitions/api.CardDTO" 92 | } 93 | } 94 | } 95 | } 96 | } 97 | }, 98 | "/open-deck": { 99 | "get": { 100 | "description": "Returns a deck with all of its cars revealed.", 101 | "consumes": [ 102 | "*/*" 103 | ], 104 | "produces": [ 105 | "application/json" 106 | ], 107 | "summary": "Opens deck.", 108 | "parameters": [ 109 | { 110 | "type": "string", 111 | "description": "id of the deck", 112 | "name": "deck_id", 113 | "in": "query", 114 | "required": true 115 | } 116 | ], 117 | "responses": { 118 | "200": { 119 | "description": "OK", 120 | "schema": { 121 | "$ref": "#/definitions/api.OpenDeckDTO" 122 | } 123 | } 124 | } 125 | } 126 | } 127 | }, 128 | "definitions": { 129 | "api.CardDTO": { 130 | "type": "object", 131 | "properties": { 132 | "code": { 133 | "type": "string" 134 | }, 135 | "suit": { 136 | "type": "string" 137 | }, 138 | "value": { 139 | "type": "string" 140 | } 141 | } 142 | }, 143 | "api.ClosedDeckDTO": { 144 | "type": "object", 145 | "properties": { 146 | "deck_id": { 147 | "type": "string" 148 | }, 149 | "remaining": { 150 | "type": "integer" 151 | }, 152 | "shuffled": { 153 | "type": "boolean" 154 | } 155 | } 156 | }, 157 | "api.OpenDeckDTO": { 158 | "type": "object", 159 | "properties": { 160 | "cards": { 161 | "type": "array", 162 | "items": { 163 | "$ref": "#/definitions/api.CardDTO" 164 | } 165 | }, 166 | "deck_id": { 167 | "type": "string" 168 | }, 169 | "remaining": { 170 | "type": "integer" 171 | }, 172 | "shuffled": { 173 | "type": "boolean" 174 | } 175 | } 176 | } 177 | } 178 | } -------------------------------------------------------------------------------- /docs/swagger.yaml: -------------------------------------------------------------------------------- 1 | basePath: / 2 | definitions: 3 | api.CardDTO: 4 | properties: 5 | code: 6 | type: string 7 | suit: 8 | type: string 9 | value: 10 | type: string 11 | type: object 12 | api.ClosedDeckDTO: 13 | properties: 14 | deck_id: 15 | type: string 16 | remaining: 17 | type: integer 18 | shuffled: 19 | type: boolean 20 | type: object 21 | api.OpenDeckDTO: 22 | properties: 23 | cards: 24 | items: 25 | $ref: '#/definitions/api.CardDTO' 26 | type: array 27 | deck_id: 28 | type: string 29 | remaining: 30 | type: integer 31 | shuffled: 32 | type: boolean 33 | type: object 34 | host: localhost:8080 35 | info: 36 | contact: 37 | email: support@swagger.io 38 | name: API Support 39 | url: http://www.swagger.io/support 40 | description: This is a sample server server. 41 | license: 42 | name: Apache 2.0 43 | url: http://www.apache.org/licenses/LICENSE-2.0.html 44 | termsOfService: http://swagger.io/terms/ 45 | title: Deck Management API 46 | version: "0.1" 47 | paths: 48 | /create-deck: 49 | post: 50 | consumes: 51 | - '*/*' 52 | description: Creates deck that can be either shuffled or unshuffled. It can 53 | accept the list of exact cards which can be shuffled or unshuffled as well. 54 | In case no cards provided it returns a deck with 52 cards. 55 | parameters: 56 | - description: indicates whether deck is shuffled 57 | in: query 58 | name: shuffled 59 | required: true 60 | type: boolean 61 | - description: array of card codes i.e. 8C,AS,7D 62 | in: query 63 | name: cards 64 | type: array 65 | produces: 66 | - application/json 67 | responses: 68 | "200": 69 | description: OK 70 | schema: 71 | $ref: '#/definitions/api.ClosedDeckDTO' 72 | summary: Creates new deck. 73 | /draw-cards: 74 | put: 75 | consumes: 76 | - '*/*' 77 | description: Removes given number of cards from a deck and returns them as a 78 | response. 79 | parameters: 80 | - description: id of the deck 81 | in: query 82 | name: deck_id 83 | required: true 84 | type: string 85 | - description: number of cards to draw 86 | in: query 87 | name: count 88 | required: true 89 | type: integer 90 | produces: 91 | - application/json 92 | responses: 93 | "200": 94 | description: OK 95 | schema: 96 | items: 97 | $ref: '#/definitions/api.CardDTO' 98 | type: array 99 | summary: Draws cards from a deck. 100 | /open-deck: 101 | get: 102 | consumes: 103 | - '*/*' 104 | description: Returns a deck with all of its cars revealed. 105 | parameters: 106 | - description: id of the deck 107 | in: query 108 | name: deck_id 109 | required: true 110 | type: string 111 | produces: 112 | - application/json 113 | responses: 114 | "200": 115 | description: OK 116 | schema: 117 | $ref: '#/definitions/api.OpenDeckDTO' 118 | summary: Opens deck. 119 | schemes: 120 | - http 121 | swagger: "2.0" 122 | -------------------------------------------------------------------------------- /domain/card.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | type Card struct { 4 | Rank Rank 5 | Shape Shape 6 | } 7 | 8 | func CreateCard(rank Rank, shape Shape) Card { 9 | return Card{ 10 | Rank: rank, 11 | Shape: shape, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /domain/deck.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | "errors" 5 | "math/rand" 6 | "time" 7 | 8 | "github.com/google/uuid" 9 | "go.uber.org/zap" 10 | ) 11 | 12 | type Deck struct { 13 | DeckId uuid.UUID 14 | Shuffled bool 15 | Cards []Card 16 | } 17 | 18 | var unshuffledCards []Card 19 | 20 | func CountRemainingCards(d Deck) uint8 { 21 | return uint8(len(d.Cards)) 22 | } 23 | 24 | func generateUnshuffledCards() []Card { 25 | var res []Card 26 | 27 | for i := Spades; i <= Hearts; i++ { 28 | for j := Ace; j <= King; j++ { 29 | res = append(res, CreateCard(j, i)) 30 | } 31 | } 32 | return res 33 | } 34 | 35 | func shuffleCards(cards []Card) { 36 | rand.Seed(time.Now().UnixNano()) 37 | rand.Shuffle(len(cards), func(i, j int) { cards[i], cards[j] = cards[j], cards[i] }) 38 | } 39 | 40 | func initCards() []Card { 41 | cards := make([]Card, len(unshuffledCards)) 42 | copy(cards, unshuffledCards) 43 | return cards 44 | } 45 | 46 | func CreateDeck(shuffled bool, cards ...Card) Deck { 47 | logger, _ := zap.NewProduction() 48 | defer logger.Sync() 49 | sugar := logger.Sugar() 50 | sugar.Infow("Create deck.", "shuffled", shuffled, "cards", cards) 51 | if len(cards) == 0 { 52 | cards = initCards() 53 | } 54 | if shuffled { 55 | shuffleCards(cards) 56 | } 57 | result := Deck{ 58 | DeckId: uuid.New(), 59 | Shuffled: shuffled, 60 | Cards: cards, 61 | } 62 | sugar.Infow("Create deck completed", "deck", result) 63 | return result 64 | } 65 | 66 | func DrawCards(deck *Deck, count uint8) ([]Card, error) { 67 | logger, _ := zap.NewProduction() 68 | defer logger.Sync() 69 | sugar := logger.Sugar() 70 | sugar.Infow("Draw cards.", "deck", deck, "count", count) 71 | if count > CountRemainingCards(*deck) { 72 | err := errors.New("insuffucient amount of cards in deck") 73 | sugar.Errorw("Draw cards completed.", "err", err, "deck", deck) 74 | return nil, err 75 | } 76 | result := deck.Cards[:count] 77 | deck.Cards = deck.Cards[count:] 78 | sugar.Infow("Draw cards completed.", "result", result, "deck", deck) 79 | return result, nil 80 | } 81 | -------------------------------------------------------------------------------- /domain/deck_test.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | type CreateDeckDataItem struct { 10 | index int 11 | shape Shape 12 | rank Rank 13 | } 14 | 15 | var CreateUnshuffledDeckData = []CreateDeckDataItem{ 16 | {0, Spades, Ace}, 17 | {1, Spades, Two}, 18 | {2, Spades, Three}, 19 | {3, Spades, Four}, 20 | {4, Spades, Five}, 21 | {5, Spades, Six}, 22 | {6, Spades, Seven}, 23 | {7, Spades, Eight}, 24 | {8, Spades, Nine}, 25 | {9, Spades, Ten}, 26 | {10, Spades, Jack}, 27 | {11, Spades, Queen}, 28 | {12, Spades, King}, 29 | {13, Diamonds, Ace}, 30 | {14, Diamonds, Two}, 31 | {15, Diamonds, Three}, 32 | {16, Diamonds, Four}, 33 | {17, Diamonds, Five}, 34 | {18, Diamonds, Six}, 35 | {19, Diamonds, Seven}, 36 | {20, Diamonds, Eight}, 37 | {21, Diamonds, Nine}, 38 | {22, Diamonds, Ten}, 39 | {23, Diamonds, Jack}, 40 | {24, Diamonds, Queen}, 41 | {25, Diamonds, King}, 42 | {26, Clubs, Ace}, 43 | {27, Clubs, Two}, 44 | {28, Clubs, Three}, 45 | {29, Clubs, Four}, 46 | {30, Clubs, Five}, 47 | {31, Clubs, Six}, 48 | {32, Clubs, Seven}, 49 | {33, Clubs, Eight}, 50 | {34, Clubs, Nine}, 51 | {35, Clubs, Ten}, 52 | {36, Clubs, Jack}, 53 | {37, Clubs, Queen}, 54 | {38, Clubs, King}, 55 | {39, Hearts, Ace}, 56 | {40, Hearts, Two}, 57 | {41, Hearts, Three}, 58 | {42, Hearts, Four}, 59 | {43, Hearts, Five}, 60 | {44, Hearts, Six}, 61 | {45, Hearts, Seven}, 62 | {46, Hearts, Eight}, 63 | {47, Hearts, Nine}, 64 | {48, Hearts, Ten}, 65 | {49, Hearts, Jack}, 66 | {50, Hearts, Queen}, 67 | {51, Hearts, King}, 68 | } 69 | 70 | func TestCreateDeck_Unshuffled(t *testing.T) { 71 | unshuffledDeck := CreateDeck(false) 72 | assert.False(t, unshuffledDeck.Shuffled) 73 | for _, item := range CreateUnshuffledDeckData { 74 | card := unshuffledDeck.Cards[item.index] 75 | assert.Equal(t, item.rank, card.Rank) 76 | assert.Equal(t, item.shape, card.Shape) 77 | } 78 | } 79 | 80 | func TestCreateDeck_Shuffled(t *testing.T) { 81 | shuffledDeck := CreateDeck(true) 82 | assert.True(t, shuffledDeck.Shuffled) 83 | cardCount := make(map[Card]int) 84 | for _, card := range shuffledDeck.Cards { 85 | value, exists := cardCount[card] 86 | if exists { 87 | value++ 88 | cardCount[card] = value 89 | } else { 90 | cardCount[card] = 1 91 | } 92 | } 93 | for _, oracle := range unshuffledCards { 94 | value, exists := cardCount[oracle] 95 | if exists { 96 | if value != 1 { 97 | t.Log("Expecting each card to be present only once") 98 | t.Fail() 99 | } 100 | } else { 101 | t.Log("Expecting each card to be present") 102 | t.Fail() 103 | } 104 | } 105 | } 106 | 107 | func TestCreateDeck_Shuffled_ThenUnshuffled(t *testing.T) { 108 | shuffledDeck := CreateDeck(true) 109 | assert.True(t, shuffledDeck.Shuffled) 110 | unshuffledDeck := CreateDeck(false) 111 | for _, item := range CreateUnshuffledDeckData { 112 | card := unshuffledDeck.Cards[item.index] 113 | assert.Equal(t, item.rank, card.Rank) 114 | assert.Equal(t, item.shape, card.Shape) 115 | } 116 | } 117 | 118 | func TestCreateDeck_ExactCardsArePassed_Unshuffled(t *testing.T) { 119 | jackOfDiamonds := CreateCard(Jack, Diamonds) 120 | aceOfSpades := CreateCard(Ace, Spades) 121 | queenOfHearts := CreateCard(Queen, Hearts) 122 | cards := []Card{jackOfDiamonds, aceOfSpades, queenOfHearts} 123 | deck := CreateDeck(false, cards...) 124 | for i, inputCard := range cards { 125 | card := deck.Cards[i] 126 | assert.Equal(t, inputCard, card) 127 | } 128 | } 129 | 130 | func TestCreateDeck_ExactCardsArePassed_Shuffled(t *testing.T) { 131 | jackOfDiamonds := CreateCard(Jack, Diamonds) 132 | aceOfSpades := CreateCard(Ace, Spades) 133 | queenOfHearts := CreateCard(Queen, Hearts) 134 | cards := []Card{jackOfDiamonds, aceOfSpades, queenOfHearts} 135 | deck := CreateDeck(false, cards...) 136 | deckCardsCount := make(map[Card]int) 137 | for _, resCard := range deck.Cards { 138 | value, exists := deckCardsCount[resCard] 139 | if exists { 140 | value++ 141 | deckCardsCount[resCard] = value 142 | } else { 143 | deckCardsCount[resCard] = 1 144 | } 145 | } 146 | for _, inputCard := range cards { 147 | value, found := deckCardsCount[inputCard] 148 | assert.True(t, found, "Expected all cards to be present") 149 | assert.Equal(t, 1, value, "Expected cards not to be duplicate") 150 | } 151 | } 152 | 153 | func TestDrawCards_SufficientAmount(t *testing.T) { 154 | deck := CreateDeck(true) 155 | items, err := DrawCards(&deck, 2) 156 | assert.Nil(t, err) 157 | assert.Equal(t, 2, len(items)) 158 | remaining := CountRemainingCards(deck) 159 | assert.Equal(t, uint8(50), remaining) 160 | } 161 | 162 | func TestDrawCards_SufficientAmount_NoItemsRemain(t *testing.T) { 163 | deck := CreateDeck(true) 164 | items, err := DrawCards(&deck, 52) 165 | assert.Nil(t, err) 166 | assert.Equal(t, 52, len(items)) 167 | reamining := CountRemainingCards(deck) 168 | assert.Equal(t, uint8(0), reamining) 169 | } 170 | 171 | func TestDrawCards_InsufficientAmount(t *testing.T) { 172 | deck := CreateDeck(true) 173 | _, err := DrawCards(&deck, 53) 174 | assert.NotNil(t, err) 175 | } 176 | -------------------------------------------------------------------------------- /domain/init.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | func init() { 4 | unshuffledCards = generateUnshuffledCards() 5 | } 6 | -------------------------------------------------------------------------------- /domain/rank.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | type Rank int8 4 | 5 | const ( 6 | Ace Rank = iota 7 | Two 8 | Three 9 | Four 10 | Five 11 | Six 12 | Seven 13 | Eight 14 | Nine 15 | Ten 16 | Jack 17 | Queen 18 | King 19 | ) 20 | -------------------------------------------------------------------------------- /domain/shape.go: -------------------------------------------------------------------------------- 1 | package domain 2 | 3 | type Shape uint8 4 | 5 | const ( 6 | Spades Shape = iota 7 | Diamonds 8 | Clubs 9 | Hearts 10 | ) 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module toggl-deck-management-api 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.8.1 // indirect 7 | github.com/google/uuid v1.3.0 8 | github.com/stretchr/testify v1.8.1 // indirect 9 | github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect 10 | github.com/swaggo/gin-swagger v1.5.0 // indirect 11 | github.com/swaggo/swag v1.8.3 // indirect 12 | go.uber.org/atomic v1.11.0 // indirect 13 | go.uber.org/multierr v1.11.0 // indirect 14 | go.uber.org/zap v1.24.0 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= 3 | github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= 4 | github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= 5 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 6 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= 7 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 8 | github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= 9 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 10 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= 11 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 12 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 13 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 15 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= 17 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 18 | github.com/gin-contrib/gzip v0.0.5/go.mod h1:OPIK6HR0Um2vNmBUTlayD7qle4yVVRZT0PyhdUigrKk= 19 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 20 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 21 | github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= 22 | github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= 23 | github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= 24 | github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= 25 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 26 | github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= 27 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 28 | github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= 29 | github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= 30 | github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= 31 | github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= 32 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 33 | github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= 34 | github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= 35 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 36 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= 37 | github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= 38 | github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= 39 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 40 | github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= 41 | github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= 42 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= 43 | github.com/go-playground/validator/v10 v10.10.0 h1:I7mrTYv78z8k8VXa/qJlOlEXn/nBh+BF8dHX5nt/dr0= 44 | github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= 45 | github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= 46 | github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 47 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 48 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 49 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 50 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 51 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 52 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 53 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 54 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 55 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 56 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 57 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 58 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 59 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 60 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 61 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 62 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= 63 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 64 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 65 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 66 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= 67 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 68 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 69 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 70 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 71 | github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= 72 | github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 73 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 74 | github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= 75 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= 76 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 77 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 78 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 79 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 80 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 81 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 82 | github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= 83 | github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= 84 | github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= 85 | github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= 86 | github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= 87 | github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= 88 | github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= 89 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 90 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 91 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 92 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 93 | github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 94 | github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= 95 | github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= 96 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 97 | github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 98 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 99 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 100 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 101 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 102 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 103 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 104 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 105 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 106 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 107 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 108 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 109 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 110 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 111 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 112 | github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc= 113 | github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= 114 | github.com/swaggo/gin-swagger v1.5.0 h1:hlLbxPj6qvbtX2wpbsZuOIlcnPRCUDGccA0zMKVNpME= 115 | github.com/swaggo/gin-swagger v1.5.0/go.mod h1:3mKpZClKx7mnUGsiwJeEkNhnr1VHMkMaTAXIoFYUXrA= 116 | github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= 117 | github.com/swaggo/swag v1.8.3 h1:3pZSSCQ//gAH88lfmxM3Cd1+JCsxV8Md6f36b9hrZ5s= 118 | github.com/swaggo/swag v1.8.3/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBnseg= 119 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= 120 | github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo= 121 | github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= 122 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= 123 | github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= 124 | github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= 125 | github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= 126 | github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= 127 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 128 | github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 129 | github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 130 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 131 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 132 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 133 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 134 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 135 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 136 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 137 | go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= 138 | go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= 139 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 140 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 141 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 142 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI= 143 | golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 144 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= 145 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 146 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 147 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 148 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= 149 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 150 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 151 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 152 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= 153 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 154 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 155 | golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= 156 | golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 157 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 158 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= 159 | golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 160 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 161 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 162 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 163 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 164 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 165 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 166 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 167 | golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 168 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 169 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 170 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 171 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 172 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069 h1:siQdpVirKtzPhKl3lZWozZraCFObP8S1v6PRp0bLrtU= 173 | golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 174 | golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 175 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 176 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= 177 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 178 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 179 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 180 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 181 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 182 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 183 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 184 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 185 | golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= 186 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 187 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 188 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 189 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 190 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 191 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 192 | golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= 193 | golang.org/x/tools v0.1.10 h1:QjFRCZxdOhBJ/UNgnBZLbNV13DlbnK0quyivTnXJM20= 194 | golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= 195 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 196 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 197 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 198 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 199 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 200 | google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= 201 | google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 202 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 203 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 204 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 205 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 206 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 207 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 208 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 209 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 210 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 211 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 212 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 213 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 214 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 215 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 216 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 217 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "toggl-deck-management-api/api" 5 | _ "toggl-deck-management-api/docs" 6 | 7 | "github.com/gin-gonic/gin" 8 | swaggerFiles "github.com/swaggo/files" 9 | ginSwagger "github.com/swaggo/gin-swagger" 10 | ) 11 | 12 | // @title Deck Management API 13 | // @version 0.1 14 | // @description This is a sample server server. 15 | // @termsOfService http://swagger.io/terms/ 16 | 17 | // @contact.name API Support 18 | // @contact.url http://www.swagger.io/support 19 | // @contact.email support@swagger.io 20 | 21 | // @license.name Apache 2.0 22 | // @license.url http://www.apache.org/licenses/LICENSE-2.0.html 23 | 24 | // @host localhost:8080 25 | // @BasePath / 26 | // @schemes http 27 | func main() { 28 | gin.SetMode(gin.ReleaseMode) 29 | r := gin.New() 30 | r.Use(api.StructuredLogger()) 31 | r.Use(gin.Recovery()) 32 | r.POST("/create-deck", api.CreateDeckHandler) 33 | r.GET("/open-deck", api.OpenDeckHandler) 34 | r.PUT("/draw-cards", api.DrawCardsHandler) 35 | url := ginSwagger.URL("/swagger/doc.json") 36 | r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, url)) 37 | r.Run() 38 | } 39 | -------------------------------------------------------------------------------- /storage/storage.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "sync" 5 | "toggl-deck-management-api/domain" 6 | 7 | "github.com/google/uuid" 8 | ) 9 | 10 | var data = sync.Map{} 11 | 12 | func Add(deck domain.Deck) { 13 | data.Store(deck.DeckId, deck) 14 | } 15 | 16 | func Get(id uuid.UUID) (*domain.Deck, bool) { 17 | item, found := data.Load(id) 18 | if found { 19 | deck := item.(domain.Deck) 20 | return &deck, found 21 | } 22 | return nil, found 23 | } 24 | --------------------------------------------------------------------------------