init
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"jp.wtf/go-fishing/internal/db"
|
||||
v1 "jp.wtf/go-fishing/internal/handlers/v1"
|
||||
)
|
||||
|
||||
type Env struct {
|
||||
DB *db.Database
|
||||
}
|
||||
|
||||
func (env *Env) RegisterRoutes(rg *gin.RouterGroup) {
|
||||
v1Router := v1.V1Router{DB: env.DB}
|
||||
v1Router.RegisterRoutes(rg)
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"jp.wtf/go-fishing/internal/api"
|
||||
"jp.wtf/go-fishing/internal/models"
|
||||
)
|
||||
|
||||
func (h *V1Router) Login(c *gin.Context) {
|
||||
if api.CheckIfLoggedIn(c) {
|
||||
api.Fail(c, http.StatusBadRequest, api.APIErrorCodeInvalidRequest, "You are already logged in!")
|
||||
return
|
||||
}
|
||||
|
||||
var json struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
if err := c.ShouldBind(&json); err != nil {
|
||||
api.Fail(c, http.StatusBadRequest, api.APIErrorCodeInvalidBody, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
angler, err := h.DB.GetRawAnglerByUsername(json.Username)
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
api.Fail(c, http.StatusUnauthorized, api.APIErrorCodeUnauthorized, "Invalid username or password")
|
||||
return
|
||||
}
|
||||
|
||||
api.Fail(c, http.StatusInternalServerError, api.APIErrorCodeUnknown, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if valid := api.CheckPassword(json.Password, angler.PasswordHash); valid {
|
||||
s := sessions.Default(c)
|
||||
s.Set("angler_id", angler.ID)
|
||||
err := s.Save()
|
||||
|
||||
if err != nil {
|
||||
api.Fail(c, http.StatusInternalServerError, api.APIErrorCodeUnknown, "Failed to create new session")
|
||||
return
|
||||
}
|
||||
|
||||
api.OK(c, models.Angler{
|
||||
ID: angler.ID,
|
||||
Username: angler.Username,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
api.Fail(c, http.StatusUnauthorized, api.APIErrorCodeUnauthorized, "Invalid username or password")
|
||||
}
|
||||
|
||||
func (h *V1Router) Signup(c *gin.Context) {
|
||||
if api.CheckIfLoggedIn(c) {
|
||||
api.Fail(c, http.StatusBadRequest, api.APIErrorCodeInvalidRequest, "You are already logged in!")
|
||||
return
|
||||
}
|
||||
|
||||
var json api.CreateAnglerRequest
|
||||
if err := c.ShouldBind(&json); err != nil {
|
||||
api.Fail(c, http.StatusBadRequest, api.APIErrorCodeInvalidBody, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
hashed_password, hash_err := api.HashPassword(json.Password)
|
||||
|
||||
if hash_err != nil {
|
||||
api.Fail(c, http.StatusInternalServerError, api.APIErrorCodeUnknown, hash_err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
new_angler, create_err := h.DB.CreateNewAngler(json.Username, hashed_password)
|
||||
|
||||
if create_err != nil {
|
||||
api.Fail(c, http.StatusInternalServerError, api.APIErrorCodeUnknown, create_err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
s := sessions.Default(c)
|
||||
s.Set("angler_id", new_angler.ID)
|
||||
|
||||
if err := s.Save(); err != nil {
|
||||
api.Fail(c, http.StatusInternalServerError, api.APIErrorCodeUnknown, "Failed to create new session")
|
||||
return
|
||||
}
|
||||
|
||||
api.OK(c, models.Angler{
|
||||
ID: new_angler.ID,
|
||||
Username: new_angler.Username,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *V1Router) Logout(c *gin.Context) {
|
||||
if !api.CheckIfLoggedIn(c) {
|
||||
api.Fail(c, http.StatusBadRequest, api.APIErrorCodeInvalidRequest, "You are not logged in...thus you cannot logout!")
|
||||
return
|
||||
}
|
||||
|
||||
s := sessions.Default(c)
|
||||
s.Clear()
|
||||
s.Save()
|
||||
api.OK(c, nil)
|
||||
}
|
||||
|
||||
func (h *V1Router) GetMe(c *gin.Context) {
|
||||
angler, err := api.GetAnglerFromSession(c)
|
||||
|
||||
if err != nil {
|
||||
api.Fail(c, http.StatusInternalServerError, api.APIErrorCodeUnknown, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
api.OK(c, angler)
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"math/rand/v2"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"jp.wtf/go-fishing/internal/api"
|
||||
)
|
||||
|
||||
var sayings = []string{
|
||||
// Near Misses
|
||||
"Close but no cigar",
|
||||
"Almost only counts in horseshoes and hand grenades",
|
||||
"A day late and a dollar short",
|
||||
"A miss is as good as a mile",
|
||||
"Always a bridesmaid, never a bride",
|
||||
"Snatched defeat from the jaws of victory",
|
||||
"Fell at the last hurdle",
|
||||
"Missed the boat",
|
||||
"Missed it by a whisker",
|
||||
"So near, and yet so far",
|
||||
"A swing and a miss",
|
||||
|
||||
// Futile Efforts
|
||||
"Beating a dead horse",
|
||||
"Tilting at windmills",
|
||||
"Squeezing blood from a stone",
|
||||
"Nailing jelly to a wall",
|
||||
"Herding cats",
|
||||
"Rearranging deck chairs on the Titanic",
|
||||
"Pushing a rope",
|
||||
"Spitting in the wind",
|
||||
"Putting toothpaste back in the tube",
|
||||
"Shoveling snow in a blizzard",
|
||||
"Spinning your wheels",
|
||||
"Emptying the ocean with a teaspoon",
|
||||
"Barking up the wrong tree",
|
||||
"A wild goose chase",
|
||||
"Catching the wind in a net",
|
||||
}
|
||||
|
||||
func (h *V1Router) Cast(c *gin.Context) {
|
||||
angler, err := api.GetAnglerFromSession(c)
|
||||
|
||||
if err != nil {
|
||||
api.Fail(c, http.StatusInternalServerError, api.APIErrorCodeUnknown, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
is_casting, check_err := h.DB.IsCasting(angler.ID)
|
||||
|
||||
if check_err != nil {
|
||||
api.Fail(c, http.StatusInternalServerError, api.APIErrorCodeUnknown, "Unable to check for existing cast")
|
||||
return
|
||||
}
|
||||
|
||||
if is_casting {
|
||||
api.Fail(c, http.StatusBadRequest, api.APIErrorCodeInvalidRequest, "You are already casting! >:|")
|
||||
return
|
||||
}
|
||||
|
||||
cast, cast_create_err := h.DB.CreateNewCast(angler.ID)
|
||||
|
||||
if cast_create_err != nil {
|
||||
api.Fail(c, http.StatusInternalServerError, api.APIErrorCodeUnknown, "Unable to start your cast")
|
||||
return
|
||||
}
|
||||
|
||||
// Fishing can take anywhere from 2-5 seconds
|
||||
minTime, maxTime := 2, 5
|
||||
timeToWait := rand.IntN(maxTime-minTime+1) + minTime
|
||||
|
||||
select {
|
||||
case <-time.After(time.Duration(timeToWait) * time.Second):
|
||||
// One we complete fishing we must decide if we caught anything
|
||||
chance := 75
|
||||
gen := rand.IntN(100)
|
||||
caught := gen < chance
|
||||
|
||||
_, end_cast_err := h.DB.EndCastByID(cast.ID)
|
||||
|
||||
if end_cast_err != nil {
|
||||
api.Fail(c, http.StatusInternalServerError, api.APIErrorCodeUnknown, "Unable to update your cast — may have left you in a bad state :(")
|
||||
return
|
||||
}
|
||||
|
||||
if caught {
|
||||
// If so then we must select a fish.
|
||||
// Then we must determine a size between the min and max of said fish
|
||||
// Finally record the catch and return it
|
||||
api.OK(c, "Got one! Nice!")
|
||||
} else {
|
||||
// Otherwise return. You are left empty handed
|
||||
api.Fail(c, http.StatusNotFound, api.APIErrorCodeNotFound, sayings[rand.IntN(len(sayings))])
|
||||
}
|
||||
case <-c.Done():
|
||||
_, end_cast_err := h.DB.EndCastByID(cast.ID)
|
||||
|
||||
if end_cast_err != nil {
|
||||
api.Fail(c, http.StatusInternalServerError, api.APIErrorCodeUnknown, "Unable to update your cast — may have left you in a bad state :(")
|
||||
return
|
||||
}
|
||||
|
||||
api.Fail(c, http.StatusRequestTimeout, api.APIErrorCodeUnknown, "Cast terminiated for unknown reason")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
"jp.wtf/go-fishing/internal/api"
|
||||
"jp.wtf/go-fishing/internal/db"
|
||||
)
|
||||
|
||||
type V1Router struct {
|
||||
DB *db.Database
|
||||
}
|
||||
|
||||
func (h *V1Router) AnglerAuthMiddleware(c *gin.Context) {
|
||||
s := sessions.Default(c)
|
||||
val := s.Get("angler_id")
|
||||
|
||||
if val == nil || val == "" {
|
||||
api.FailAndAbort(c, http.StatusUnauthorized, api.APIErrorCodeUnauthorized, "You are not logged in!")
|
||||
return
|
||||
}
|
||||
|
||||
valStr, ok := val.(string)
|
||||
angler_id, uuid_err := uuid.Parse(valStr)
|
||||
|
||||
if uuid_err != nil || !ok {
|
||||
s.Clear()
|
||||
s.Save()
|
||||
api.FailAndAbort(c, http.StatusInternalServerError, api.APIErrorCodeUnknown, "Session data malformed")
|
||||
return
|
||||
}
|
||||
|
||||
angler, err := h.DB.GetAnglerByID(angler_id)
|
||||
|
||||
if err != nil {
|
||||
s.Clear()
|
||||
s.Save()
|
||||
log.Print("Failed to find angler: ", err.Error())
|
||||
api.FailAndAbort(c, http.StatusUnauthorized, api.APIErrorCodeUnauthorized, "Bad or expired session")
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("angler", angler)
|
||||
s.Save()
|
||||
}
|
||||
|
||||
func (h *V1Router) RegisterRoutes(rg *gin.RouterGroup) {
|
||||
auth := rg.Group("/auth")
|
||||
auth.POST("/login", h.Login)
|
||||
auth.POST("/signup", h.Signup)
|
||||
auth.POST("/logout", h.Logout)
|
||||
auth.GET("/me", h.GetMe)
|
||||
|
||||
rg.GET("/cast", h.AnglerAuthMiddleware, h.Cast)
|
||||
}
|
||||
Reference in New Issue
Block a user