DEVELOPMENT ENVIRONMENT

~liljamo/deck-builder

e72241012b6a224a68bee86e5e269cd2eab41aa1 — Jonni Liljamo 1 year, 10 months ago 81e1eca
feat!(sdbapi): restructure, expand user details
M sdbapi/db/game.go => sdbapi/db/game.go +2 -3
@@ 1,6 1,6 @@
/*
 * This file is part of sdbapi
 * Copyright (C) 2022 Jonni Liljamo <jonni@liljamo.com>
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.


@@ 16,11 16,10 @@ import (
// get games for a specific user with email
func GetGamesForUser(id string) ([]models.Game, *apierror.APIError) {
	var games []models.Game
	game_records := DbConn.Where("p1 = ? OR p2 = ?", id, id).Find(&games)
	game_records := DbConn.Preload("Host").Preload("Guest").Where("host_id = ? OR guest_id = ?", id, id).Find(&games)
	if game_records.Error != nil {
		return []models.Game{}, &apierror.NoGamesForUser
	}

	return games, nil
}


M sdbapi/handlers/allforming.go => sdbapi/handlers/allforming.go +2 -3
@@ 1,6 1,6 @@
/*
 * This file is part of sdbapi
 * Copyright (C) 2022 Jonni Liljamo <jonni@liljamo.com>
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.


@@ 19,7 19,7 @@ import (

func FormingGames(c *gin.Context) {
	var games []models.Game
	records := db.DbConn.Where("state = ?", models.GAMESTATE_FORMING).Find(&games)
	records := db.DbConn.Preload("Host").Preload("Guest").Where("state = ?", models.GAMESTATE_FORMING).Find(&games)
	if records.Error != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": apierror.GetAllFormingFailed})
		c.Abort()


@@ 28,4 28,3 @@ func FormingGames(c *gin.Context) {

	c.JSON(http.StatusOK, games)
}


M sdbapi/handlers/gamecreate.go => sdbapi/handlers/gamecreate.go +3 -3
@@ 1,6 1,6 @@
/*
 * This file is part of sdbapi
 * Copyright (C) 2022 Jonni Liljamo <jonni@liljamo.com>
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.


@@ 18,11 18,11 @@ import (
)

func CreateGame(c *gin.Context) {
	p1, _ := db.GetUserByEmail(c.GetString("email"))
	host, _ := db.GetUserByEmail(c.GetString("email"))

	var game models.Game

	game.P1 = p1.ID
	game.HostID = host.ID
	game.State = models.GAMESTATE_FORMING

	entry := db.DbConn.Create(&game)

M sdbapi/handlers/gameinfo.go => sdbapi/handlers/gameinfo.go +3 -3
@@ 1,6 1,6 @@
/*
 * This file is part of sdbapi
 * Copyright (C) 2022 Jonni Liljamo <jonni@liljamo.com>
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.


@@ 20,9 20,9 @@ import (
func GameInfo(c *gin.Context) {
	id := c.Param("id")

	// Check if the game exists
	// check if the game exists
	var game models.Game
	record := db.DbConn.Where("id = ?", id).First(&game)
	record := db.DbConn.Preload("Host").Preload("Guest").Where("id = ?", id).First(&game)
	if record.Error != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": apierror.GameNotFound})
		c.Abort()

M sdbapi/handlers/joingame.go => sdbapi/handlers/joingame.go +10 -19
@@ 1,6 1,6 @@
/*
 * This file is part of sdbapi
 * Copyright (C) 2022 Jonni Liljamo <jonni@liljamo.com>
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.


@@ 29,8 29,8 @@ func JoinGame(c *gin.Context) {
		return
	}

	// check if p2 is already filled
	if game.P2 != "" {
	// check if guest is already filled
	if game.GuestID != "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": apierror.GameFull})
		c.Abort()
		return


@@ 43,31 43,23 @@ func JoinGame(c *gin.Context) {
		return
	}

	// get p1 (game creator)
	p1, p1err := db.GetUserByID(game.P1)
	if p1err != nil {
	// get guest (us)
	newGuest, newGuestErr := db.GetUserByEmail(c.GetString("email"))
	if newGuestErr != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": apierror.Placeholder})
		c.Abort()
		return
	}

	// get p2 (us)
	p2, p2err := db.GetUserByEmail(c.GetString("email"))
	if p2err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": apierror.Placeholder})
		c.Abort()
		return
	}

	// are we p1?
	if p1.ID == p2.ID {
	// are we the host?
	if game.HostID == newGuest.ID {
		c.JSON(http.StatusBadRequest, gin.H{"error": apierror.CannotJoinOwnGame})
		c.Abort()
		return
	}

	// if we are not p1, set us as p2
	updatedGame := db.DbConn.Model(&game).Update("p2", p2.ID)
	// if we are not the host, set us as the guest
	updatedGame := db.DbConn.Model(&game).Update("guest_id", newGuest.ID)
	if updatedGame.Error != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": apierror.Placeholder})
		c.Abort()


@@ 77,4 69,3 @@ func JoinGame(c *gin.Context) {
	// return a 20x code
	c.JSON(http.StatusNoContent, nil)
}


M sdbapi/handlers/patchgamestate.go => sdbapi/handlers/patchgamestate.go +5 -5
@@ 20,7 20,7 @@ import (
func PatchGameState(c *gin.Context) {
	id := c.Param("id")

	// Check if the game exists
	// check if the game exists
	var game models.Game
	record := db.DbConn.Where("id = ?", id).First(&game)
	if record.Error != nil {


@@ 29,9 29,9 @@ func PatchGameState(c *gin.Context) {
		return
	}

	// Get the user who requested the patching, and verify that they are the game creator (p1)
	p1, _ := db.GetUserByEmail(c.GetString("email"))
	if game.P1 != p1.ID {
	// get the user who requested the patching, and verify that they are the game host
	presumedHost, _ := db.GetUserByEmail(c.GetString("email"))
	if game.HostID != presumedHost.ID {
		c.JSON(http.StatusUnauthorized, gin.H{"error": apierror.NotAuthorized})
		c.Abort()
		return


@@ 51,6 51,6 @@ func PatchGameState(c *gin.Context) {
		return
	}

	// Don't have anything to return
	// don't have anything to return
	c.JSON(http.StatusNoContent, nil)
}

M sdbapi/handlers/usercreate.go => sdbapi/handlers/usercreate.go +11 -6
@@ 19,22 19,22 @@ import (
)

func CreateUser(c *gin.Context) {
	var user models.User
	if err := c.ShouldBindJSON(&user); err != nil {
	var input models.UserCreateInput
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": apierror.InvalidInput})
		c.Abort()
		return
	}

	// check username
	if len(user.Username) < 3 {
	if len(input.Username) < 3 {
		c.JSON(http.StatusBadRequest, gin.H{"error": apierror.UsernameTooShort})
		c.Abort()
		return
	}

	// check email
	_, mailErr := mail.ParseAddress(user.Email)
	_, mailErr := mail.ParseAddress(input.Email)
	if mailErr != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": apierror.EmailInvalid})
		c.Abort()


@@ 42,12 42,17 @@ func CreateUser(c *gin.Context) {
	}

	// check password
	if len(user.Password) < 8 {
	if len(input.Password) < 8 {
		c.JSON(http.StatusBadRequest, gin.H{"error": apierror.PasswordTooShort})
		c.Abort()
		return
	}

	var user models.User
	user.Username = input.Username
	user.Email = input.Email
	user.Password = input.Password

	if err := user.HashPwd(user.Password); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": apierror.PasswordHashFailed})
		c.Abort()


@@ 61,5 66,5 @@ func CreateUser(c *gin.Context) {
		return
	}

	c.JSON(http.StatusCreated, gin.H{"id": user.ID, "username": user.Username, "email": user.Email})
	c.JSON(http.StatusCreated, user.ToPub())
}

M sdbapi/handlers/userinfo.go => sdbapi/handlers/userinfo.go +2 -2
@@ 1,6 1,6 @@
/*
 * This file is part of sdbapi
 * Copyright (C) 2022 Jonni Liljamo <jonni@liljamo.com>
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.


@@ 28,5 28,5 @@ func UserInfo(c *gin.Context) {
		return
	}

	c.JSON(http.StatusOK, gin.H{"id": user.ID, "username": user.Username})
	c.JSON(http.StatusOK, user.ToPub())
}

M sdbapi/models/game.go => sdbapi/models/game.go +6 -4
@@ 1,6 1,6 @@
/*
 * This file is part of sdbapi
 * Copyright (C) 2022 Jonni Liljamo <jonni@liljamo.com>
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.


@@ 22,12 22,14 @@ const (
)

type Game struct {
	ID        string `json:"id" gorm:"primarykey;type:uuid;default:gen_random_uuid()"`
	ID        string         `json:"id" gorm:"primarykey;type:uuid;default:gen_random_uuid()"`
	CreatedAt time.Time      `json:"created_at"`
	UpdatedAt time.Time      `json:"updated_at"`
	DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
	P1        string         `json:"p1" gorm:"foreignkey:ID"`
	P2        string         `json:"p2" gorm:"foreignkey:ID"`
	HostID    string         `json:"host_id"`
	Host      User           `json:"host" gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
	GuestID   string         `json:"guest_id" gorm:"default:NULL;"`
	Guest     User           `json:"guest" gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
	State     uint8          `json:"state" gorm:"type:smallint"`
	EndedAt   time.Time      `json:"ended_at" gorm:"type:timestamptz"`
}

M sdbapi/models/user.go => sdbapi/models/user.go +28 -5
@@ 1,6 1,6 @@
/*
 * This file is part of sdbapi
 * Copyright (C) 2022 Jonni Liljamo <jonni@liljamo.com>
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.


@@ 16,13 16,19 @@ import (
)

type User struct {
	ID        string `json:"id" gorm:"primarykey;type:uuid;default:gen_random_uuid()"`
	ID        string         `json:"id" gorm:"primarykey;type:uuid;default:gen_random_uuid()"`
	CreatedAt time.Time      `json:"created_at"`
	UpdatedAt time.Time      `json:"updated_at"`
	DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
	DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
	Username  string         `json:"username" gorm:"unique"`
	Email     string         `json:"email" gorm:"unique"`
	Password  string         `json:"password"`
	Email     string         `json:"-" gorm:"unique"`
	Password  string         `json:"-"`
}

type UserCreateInput struct {
	Username string `json:"username"`
	Email    string `json:"email"`
	Password string `json:"password"`
}

// hash a users password


@@ 42,3 48,20 @@ func (user *User) VerifyPwd(password string) error {
	}
	return nil
}

type UserPub struct {
	ID        string    `json:"id"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
	Username  string    `json:"username"`
}

// Convert a user to UserPub, which can be returned safely
func (user *User) ToPub() UserPub {
	return UserPub{
		ID:        user.ID,
		CreatedAt: user.CreatedAt,
		UpdatedAt: user.UpdatedAt,
		Username:  user.Username,
	}
}