DEVELOPMENT ENVIRONMENT

~liljamo/deck-builder

608c1bf3a79aed8b0bf2432203d7949e58e62ffa — Jonni Liljamo 1 year, 8 months ago 8d0903d
rework: rename sdbapi to api, redo data structure
32 files changed, 125 insertions(+), 111 deletions(-)

R sdbapi/Dockerfile => api/Dockerfile
R sdbapi/LICENSE => api/LICENSE
R sdbapi/README.md => api/README.md
R sdbapi/apierror/apierror.go => api/apierror/apierror.go
R sdbapi/auth/auth.go => api/auth/auth.go
R sdbapi/db/db.go => api/db/db.go
R sdbapi/db/game.go => api/db/game.go
R sdbapi/db/user.go => api/db/user.go
R sdbapi/dev-launch-alpine.sh => api/dev-launch-alpine.sh
R sdbapi/dev-launch.sh => api/dev-launch.sh
R sdbapi/docker-compose.yaml => api/docker-compose.yaml
R sdbapi/go.mod => api/go.mod
R sdbapi/go.sum => api/go.sum
R sdbapi/handlers/allforming.go => api/handlers/allforming.go
A api/handlers/createaction.go
R sdbapi/handlers/gamecreate.go => api/handlers/gamecreate.go
R sdbapi/handlers/gameinfo.go => api/handlers/gameinfo.go
R sdbapi/handlers/joingame.go => api/handlers/joingame.go
R sdbapi/handlers/misc.go => api/handlers/misc.go
R sdbapi/handlers/mygames.go => api/handlers/mygames.go
R sdbapi/handlers/patchgamestate.go => api/handlers/patchgamestate.go
R sdbapi/handlers/usercreate.go => api/handlers/usercreate.go
R sdbapi/handlers/userinfo.go => api/handlers/userinfo.go
R sdbapi/handlers/userinfop.go => api/handlers/userinfop.go
R sdbapi/handlers/usertoken.go => api/handlers/usertoken.go
R sdbapi/main.go => api/main.go
R sdbapi/main_test.go => api/main_test.go
R sdbapi/middlewares/auth.go => api/middlewares/auth.go
R sdbapi/models/action.go => api/models/action.go
R sdbapi/models/game.go => api/models/game.go
R sdbapi/models/user.go => api/models/user.go
D sdbapi/models/gamedata.go
R sdbapi/Dockerfile => api/Dockerfile +0 -0
R sdbapi/LICENSE => api/LICENSE +2 -2
@@ 1,5 1,5 @@
sdbapi is an API for a deck building game.
Copyright (C) 2022 Jonni Liljamo <jonni@liljamo.com>
The API for Laurelin.
Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.


R sdbapi/README.md => api/README.md +2 -2
@@ 1,7 1,7 @@
# Deck Builder API
# Laurelin API

## Deployment
Configure `SDBAPI_JWT_SECRET` in `docker-compose.yaml` to be randomized.
Configure `LAURELINAPI_JWT_SECRET` in `docker-compose.yaml` to be randomized.

## API Doc
Paths may change in the future.

R sdbapi/apierror/apierror.go => api/apierror/apierror.go +6 -1
@@ 1,5 1,5 @@
/*
 * This file is part of sdbapi
 * This file is part of laurelin_api
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.


@@ 42,6 42,11 @@ var (
	GameFull             APIError = APIError{3007, "GameFull", "game is full"}
)

// action related errors
var (
	ActionCreationFailed APIError = APIError{4000, "ActionCreationFailed", "action creation failed"}
)

type APIError struct {
	ID          uint16 `json:"id"`
	Name        string `json:"name"`

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


@@ 17,7 17,7 @@ import (
	"github.com/golang-jwt/jwt/v4"
)

var JWTSecret = []byte(os.Getenv("SDBAPI_JWT_SECRET"))
var JWTSecret = []byte(os.Getenv("LAURELINAPI_JWT_SECRET"))

type JWTClaims struct {
	Username string `json:"username"`

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


@@ 33,5 33,4 @@ func Migrate() {
	DbConn.AutoMigrate(&models.User{})
	DbConn.AutoMigrate(&models.Game{})
	DbConn.AutoMigrate(&models.Action{})
	DbConn.AutoMigrate(&models.GameData{})
}

R sdbapi/db/game.go => api/db/game.go +1 -1
@@ 1,5 1,5 @@
/*
 * This file is part of sdbapi
 * This file is part of laurelin_api
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.

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

R sdbapi/dev-launch-alpine.sh => api/dev-launch-alpine.sh +0 -0
R sdbapi/dev-launch.sh => api/dev-launch.sh +2 -2
@@ 1,6 1,6 @@
#!/bin/bash

export COMPOSE_PROJECT_NAME=sdbapi
export COMPOSE_PROJECT_NAME=laurelinapi

function confirm() {
  echo -n "$@ [y/N]: "


@@ 18,7 18,7 @@ function confirm() {
}

confirm reset containers? && docker compose down
confirm reset database? && docker volume rm sdbapi_apidb_data
confirm reset database? && docker volume rm laurelinapi_apidb_data

confirm rebuild api? && docker compose build


R sdbapi/docker-compose.yaml => api/docker-compose.yaml +11 -11
@@ 9,9 9,9 @@ volumes:
    driver: local

services:
  sdbapidb:
  laurelinapidb:
    image: postgres:15.1-alpine
    container_name: sdbapidb
    container_name: laurelinapidb
    restart: always
    networks:
      - internal


@@ 20,14 20,14 @@ services:
    volumes:
      - apidb_data:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: sdbapi
      POSTGRES_PASSWORD: sdbapi
      POSTGRES_DB: sdbapi
      POSTGRES_USER: lau
      POSTGRES_PASSWORD: lau
      POSTGRES_DB: laurelinapi

  sdbapi:
  laurelinapi:
    build: .
    image: sdbapi
    container_name: sdbapi
    image: laurelinapi
    container_name: laurelinapi
    restart: always
    networks:
      - internal


@@ 35,7 35,7 @@ services:
      - "8080:3000"
    environment:
      GIN_MODE: "release" # or "debug" for debug logs
      SDBAPI_JWT_SECRET: "XpNYdG7vgvgPPuezrtZqt4CJIUuxNP7c"
      GORM_DB_STRING: "host=sdbapidb user=sdbapi password=sdbapi dbname=sdbapi port=5432 sslmode=disable"
      LAURELINAPI_JWT_SECRET: "XpNYdG7vgvgPPuezrtZqt4CJIUuxNP7c"
      GORM_DB_STRING: "host=laurelinapidb user=lau password=lau dbname=laurelinapi port=5432 sslmode=disable"
    depends_on:
      - sdbapidb
      - laurelinapidb

R sdbapi/go.mod => api/go.mod +0 -0
R sdbapi/go.sum => api/go.sum +0 -0
R sdbapi/handlers/allforming.go => api/handlers/allforming.go +1 -1
@@ 1,5 1,5 @@
/*
 * This file is part of sdbapi
 * This file is part of laurelin_api
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.

A api/handlers/createaction.go => api/handlers/createaction.go +44 -0
@@ 0,0 1,44 @@
/*
 * This file is part of laurelin_api
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.
 */

package handlers

import (
	"api/db"
	"api/models"
	"api/apierror"
	"net/http"

	"gorm.io/datatypes"
	"github.com/gin-gonic/gin"
)

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

	var action models.Action

	action.GameID = input.GameID
	action.Invoker = input.Invoker
	action.Target = input.Target
	action.Command = datatypes.JSON(input.Command)

	entry := db.DbConn.Create(&action)
	if entry.Error != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": apierror.ActionCreationFailed})
		c.Abort()
		return
	}

	c.JSON(http.StatusCreated, action)
}

R sdbapi/handlers/gamecreate.go => api/handlers/gamecreate.go +1 -1
@@ 1,5 1,5 @@
/*
 * This file is part of sdbapi
 * This file is part of laurelin_api
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.

R sdbapi/handlers/gameinfo.go => api/handlers/gameinfo.go +2 -2
@@ 1,5 1,5 @@
/*
 * This file is part of sdbapi
 * This file is part of laurelin_api
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.


@@ 22,7 22,7 @@ func GameInfo(c *gin.Context) {

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

R sdbapi/handlers/joingame.go => api/handlers/joingame.go +1 -1
@@ 1,5 1,5 @@
/*
 * This file is part of sdbapi
 * This file is part of laurelin_api
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.

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

R sdbapi/handlers/mygames.go => api/handlers/mygames.go +1 -1
@@ 1,5 1,5 @@
/*
 * This file is part of sdbapi
 * This file is part of laurelin_api
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.

R sdbapi/handlers/patchgamestate.go => api/handlers/patchgamestate.go +1 -1
@@ 1,5 1,5 @@
/*
 * This file is part of sdbapi
 * This file is part of laurelin_api
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.

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

R sdbapi/handlers/userinfo.go => api/handlers/userinfo.go +1 -1
@@ 1,5 1,5 @@
/*
 * This file is part of sdbapi
 * This file is part of laurelin_api
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.

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

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

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


@@ 14,7 14,6 @@ import (
	"api/middlewares"
	"log"
	"os"
	"syscall"

	"github.com/gin-gonic/gin"
)


@@ 23,11 22,6 @@ func main() {
	// stores various errors during startup
	var err error

	err = setUlimits()
	if err != nil {
		log.Fatal("failed to set Ulimits")
	}

	dbConnectionString := os.Getenv("GORM_DB_STRING")
	if dbConnectionString == "" {
		log.Fatal("environment variable 'GORM_DB_STRING' is not set")


@@ 52,7 46,7 @@ func createRouter() *gin.Engine {
		api.GET("/info", handlers.Info)
		user := api.Group("/user")
		{
			user.POST("/register", handlers.CreateUser)
			user.POST("/", handlers.CreateUser)
			user.POST("/token", handlers.GenerateToken)
			user.GET("/:id", handlers.UserInfo)
			userp := user.Group("/_").Use(middlewares.Auth())


@@ 64,21 58,16 @@ func createRouter() *gin.Engine {
		{
			game.GET("/:id", handlers.GameInfo)
			game.GET("/all_forming", handlers.FormingGames)
			game.POST("/create", handlers.CreateGame)
			game.POST("/", handlers.CreateGame)
			game.PATCH("/:id/state", handlers.PatchGameState)
			game.GET("/my_games", handlers.MyGames)
			game.POST("/:id/join", handlers.JoinGame)
		}
		action := api.Group("/game/action").Use(middlewares.Auth())
		{
			action.POST("/", handlers.CreateAction)
		}
	}
	return router
}

// Set NOFILE limit to max, to allow more concurrent websocket connections
func setUlimits() error {
	var rlimit syscall.Rlimit
	if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit); err != nil {
		return err
	}
	rlimit.Cur = rlimit.Max
	return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit)
}

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

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

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


@@ 20,8 20,15 @@ type Action struct {
	CreatedAt  time.Time      `json:"created_at"`
	UpdatedAt  time.Time      `json:"updated_at"`
	DeletedAt  gorm.DeletedAt `json:"-" gorm:"index"`
	GameDataID string         `json:"game_data_id"`
	GameID     string         `json:"game_id"`
	Invoker    string         `json:"invoker"`
	Data       datatypes.JSON `json:"data"`
	Timestamp  time.Time      `json:"timestamp" gorm:"type:timestamptz"`
	Target     string         `json:"target"`
	Command    datatypes.JSON `json:"command"`
}

type PostAction struct {
	GameID  string `json:"game_id"` 
	Invoker string `json:"invoker"`
	Target  string `json:"target"`
	Command string `json:"command"`
}

R sdbapi/models/game.go => api/models/game.go +9 -4
@@ 1,5 1,5 @@
/*
 * This file is part of sdbapi
 * This file is part of laurelin_api
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.


@@ 21,17 21,22 @@ const (
	GAMESTATE_CANCELLED  uint8 = 3
)

const (
	GAMETURN_HOST  uint8 = 0
	GAMETURN_GUEST uint8 = 1
)

type Game struct {
	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:"-" gorm:"index"`
	EndedAt    time.Time      `json:"ended_at" gorm:"type:timestamptz"`
	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"`
	GameDataID string         `json:"game_data_id" gorm:"default:NULL;"`
	GameData   GameData       `json:"game_data" gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
	Turn			 uint8          `json:"turn" gorm:"type:smallint"`
	Actions    []Action       `json:"actions" gorm:"foreignKey:GameID"`
}

R sdbapi/models/user.go => api/models/user.go +1 -1
@@ 1,5 1,5 @@
/*
 * This file is part of sdbapi
 * This file is part of laurelin_api
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.

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

package models

import (
	"time"

	"gorm.io/datatypes"
	"gorm.io/gorm"
)

const (
	GAMETURN_HOST  uint8 = 0
	GAMETURN_GUEST uint8 = 1
)

type GameData struct {
	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:"-" gorm:"index"`
	Actions   []Action       `json:"actions" gorm:"foreignKey:GameDataID"`
	Turn      uint8          `json:"turn" gorm:"type:smallint"`
	// NOTE: These are not final
	HostHand  datatypes.JSON `json:"host_hand"`
	HostDeck  datatypes.JSON `json:"host_deck"`
	GuestHand datatypes.JSON `json:"guest_hand"`
	GuestDeck datatypes.JSON `json:"guest_deck"`
}