github.com/tcotav/boggle@v0.0.0-20231023231124-a86497006536/cmd/bogglesvc/service.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"unicode"
     6  
     7  	"github.com/gin-gonic/gin"
     8  	"github.com/rs/zerolog/log"
     9  	"github.com/tcotav/boggle/middleware"
    10  	"github.com/tcotav/boggle/solver"
    11  	"github.com/tcotav/boggle/wordlookup"
    12  )
    13  
    14  type Server struct {
    15  	Lookup *wordlookup.Dictionary
    16  }
    17  
    18  func main() {
    19  	// TODO - set up config, currently everything is hard coded
    20  	lookup, err := wordlookup.NewDictionaryFromFile("data/words_alpha.txt")
    21  	if err != nil {
    22  		// no dictionary means we want to stop the service
    23  		// log.Panic vs. log.Fatal - panic will allow any defered to finish, fatal will not. it will os.Exit(1)
    24  		log.Panic().Str("App", "bogglesvc").Str("Call", "main").Err(err).Msg("")
    25  	}
    26  	// set up our server
    27  	s := Server{Lookup: lookup}
    28  
    29  	ginMode := "release"
    30  	gin.SetMode(ginMode) // need to set this to turn off default DEBUG noise from gin logging
    31  	router := gin.New()
    32  	// set our custom request logger here w/gin
    33  	router.Use(middleware.RequestLogger())
    34  	// lose this when we switched from gin.Default() to gin.New()
    35  	// so add back, recovers from panics and 500s if there is one
    36  	router.Use(gin.Recovery())
    37  
    38  	// hack in some dummy handler to test basics
    39  	router.GET("/ping", func(c *gin.Context) {
    40  		c.JSON(200, gin.H{
    41  			"message": "pong",
    42  		})
    43  	})
    44  
    45  	// here's our boggle
    46  	router.POST("/boggle", s.PostBoggle)
    47  	log.Info().Str("App", "bogglesvc").Str("Call", "main").Msg("Starting server on port 8080")
    48  	router.Run(":8080")
    49  }
    50  
    51  // BoggleRequest is the request body for the POST /boggle endpoint
    52  type BoggleRequest struct {
    53  	Matrix [][]rune `json:"matrix"`
    54  }
    55  
    56  // utility function to make sure our 4x4 matrix has valid characters
    57  func validateInput(matrix [][]rune) bool {
    58  	// make sure we have a 4x4 matrix
    59  	if len(matrix) != 4 {
    60  		return false
    61  	}
    62  
    63  	// all the rows are length 4
    64  	for i := 0; i < len(matrix); i++ {
    65  		if len(matrix[i]) != 4 {
    66  			return false
    67  		}
    68  	}
    69  
    70  	// then test the runes to ensure they're valid
    71  	for i := 0; i < len(matrix); i++ {
    72  		for j := 0; j < len(matrix[i]); j++ {
    73  			// check for valid characters - matches upper and lower A-Z
    74  			if !unicode.IsLetter(matrix[i][j]) {
    75  				return false
    76  			}
    77  		}
    78  	}
    79  	return true
    80  }
    81  
    82  func (s *Server) PostBoggle(c *gin.Context) {
    83  	// get the request body
    84  	var boggleRequest BoggleRequest
    85  	// Decode the JSON request
    86  	// TODO -- this unmarshal fails w/ "cannot unmarshal string into Go struct field BoggleRequest.matrix of type int32"
    87  	decoder := json.NewDecoder(c.Request.Body)
    88  	if err := decoder.Decode(&boggleRequest); err != nil {
    89  		log.Error().Str("App", "bogglesvc").Str("Call", "postBoggle").Err(err).Msg("Error parsing request body")
    90  		c.JSON(400, gin.H{"message": "Error parsing request body"})
    91  		return
    92  	}
    93  	/*  variation tried -- same error as above but uses a gin helper method
    94  
    95  	//err := c.BindJSON(&boggleRequest)
    96  	if err != nil {
    97  		log.Error().Str("App", "bogglesvc").Str("Call", "postBoggle").Err(err).Msg("Error parsing request body")
    98  		c.JSON(400, gin.H{"message": "Error parsing request body"})
    99  		return
   100  	}*/
   101  	if !validateInput(boggleRequest.Matrix) {
   102  		log.Error().Str("App", "bogglesvc").Str("Call", "postBoggle").Msg("Invalid input")
   103  		c.JSON(400, gin.H{"message": "Invalid input"})
   104  		return
   105  	}
   106  
   107  	// get the matrix
   108  	matrix := boggleRequest.Matrix
   109  
   110  	// process the matrix
   111  	b := solver.NewBoggleSolver(matrix, s.Lookup)
   112  	words := b.FindWords()
   113  
   114  	// return the words
   115  	c.JSON(200, gin.H{"words": words})
   116  }