diff --git a/config.default.yml b/config.default.yml index f1f6891..91ad61d 100644 --- a/config.default.yml +++ b/config.default.yml @@ -1,4 +1,6 @@ --- +server: + port: 8080 database: host: localhost password: root diff --git a/config/config.go b/config/config.go index 124c81f..638defe 100644 --- a/config/config.go +++ b/config/config.go @@ -8,6 +8,7 @@ import ( ) type Configuration struct { + Server ServerConfiguration `yaml:"server"` Database DatabaseConfiguration `yaml:"database"` Features FeaturesConfiguration `yaml:"features"` Path PathConfiguration `yaml:"path"` @@ -18,6 +19,10 @@ type PathConfiguration struct { Storage string `yaml:"storage"` } +type ServerConfiguration struct { + Port int `yaml:"port"` +} + type DatabaseConfiguration struct { Host string `yaml:"host"` Port int `yaml:"port"` @@ -55,3 +60,7 @@ func Features() *FeaturesConfiguration { func Path() *PathConfiguration { return ¤tConfig.Path } + +func Server() *ServerConfiguration { + return ¤tConfig.Server +} diff --git a/database/database.go b/database/database.go index 8c3c6dc..5801864 100644 --- a/database/database.go +++ b/database/database.go @@ -1,8 +1,6 @@ package database import ( - "crypto/md5" - "encoding/hex" "errors" "fmt" "github.com/google/uuid" @@ -175,31 +173,18 @@ func UploadSave(file multipart.File, game *Game) error { return nil } -func UpdateGameRevision(game *Game) error { - filePath := path.Join(config.Path().Storage, game.PathStorage) - file, err := os.Open(filePath) - if err != nil { - return err - } - defer file.Close() - - hash := md5.New() - _, err = io.Copy(hash, file) - if err != nil { - return err - } - sum := hash.Sum(nil) +func UpdateGameRevision(game *Game, hash string) error { game.Revision += 1 if game.Hash == nil { game.Hash = new(string) } - *game.Hash = hex.EncodeToString(sum) + *game.Hash = hash game.Available = true if game.LastUpdate == nil { game.LastUpdate = new(time.Time) } *game.LastUpdate = time.Now() - err = db.Save(game).Error + err := db.Save(game).Error if err != nil { return err } diff --git a/db_dump.sql b/db_dump.sql index 130d0d8..5e8627b 100644 --- a/db_dump.sql +++ b/db_dump.sql @@ -1,54 +1,54 @@ --- -------------------------------------------------------- --- Host: 127.0.0.1 --- Server version: 8.0.27 - MySQL Community Server - GPL --- Server OS: Win64 --- HeidiSQL Version: 12.0.0.6468 --- -------------------------------------------------------- - -/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; -/*!40101 SET NAMES utf8 */; -/*!50503 SET NAMES utf8mb4 */; -/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; -/*!40103 SET TIME_ZONE='+00:00' */; -/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; -/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; -/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; - - --- Dumping database structure for osc -DROP DATABASE IF EXISTS `osc`; -CREATE DATABASE IF NOT EXISTS `osc` /*!40100 DEFAULT CHARACTER SET utf8 */ /*!80016 DEFAULT ENCRYPTION='N' */; -USE `osc`; - --- Dumping structure for table osc.games -DROP TABLE IF EXISTS `games`; -CREATE TABLE IF NOT EXISTS `games` ( - `id` bigint unsigned NOT NULL AUTO_INCREMENT, - `name` varchar(255) NOT NULL DEFAULT '0', - `revision` bigint unsigned NOT NULL DEFAULT '0', - `path_storage` text NOT NULL, - `hash` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, - `last_update` datetime DEFAULT NULL, - `user_id` bigint unsigned NOT NULL DEFAULT '0', - `available` tinyint unsigned NOT NULL DEFAULT '0', - PRIMARY KEY (`id`) -) ENGINE=MyISAM DEFAULT CHARSET=utf8mb3; - --- Data exporting was unselected. - --- Dumping structure for table osc.users -DROP TABLE IF EXISTS `users`; -CREATE TABLE IF NOT EXISTS `users` ( - `id` bigint unsigned NOT NULL AUTO_INCREMENT, - `username` varchar(50) NOT NULL, - `password` binary(60) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=MyISAM AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb3; - --- Data exporting was unselected. - -/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */; -/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; -/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; -/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; -/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; +-- -------------------------------------------------------- +-- Host: 127.0.0.1 +-- Server version: 8.0.27 - MySQL Community Server - GPL +-- Server OS: Win64 +-- HeidiSQL Version: 12.0.0.6468 +-- -------------------------------------------------------- + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET NAMES utf8 */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + + +-- Dumping database structure for osc +DROP DATABASE IF EXISTS `osc`; +CREATE DATABASE IF NOT EXISTS `osc` /*!40100 DEFAULT CHARACTER SET utf8 */ /*!80016 DEFAULT ENCRYPTION='N' */; +USE `osc`; + +-- Dumping structure for table osc.games +DROP TABLE IF EXISTS `games`; +CREATE TABLE IF NOT EXISTS `games` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(255) NOT NULL DEFAULT '0', + `revision` bigint unsigned NOT NULL DEFAULT '0', + `path_storage` text NOT NULL, + `hash` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, + `last_update` datetime DEFAULT NULL, + `user_id` bigint unsigned NOT NULL DEFAULT '0', + `available` tinyint unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) +) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3; + +-- Data exporting was unselected. + +-- Dumping structure for table osc.users +DROP TABLE IF EXISTS `users`; +CREATE TABLE IF NOT EXISTS `users` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `username` varchar(50) NOT NULL, + `password` binary(60) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3; + +-- Data exporting was unselected. + +/*!40103 SET TIME_ZONE=IFNULL(@OLD_TIME_ZONE, 'system') */; +/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; +/*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; diff --git a/server/data.go b/server/data.go index 4963732..392dfc5 100644 --- a/server/data.go +++ b/server/data.go @@ -12,6 +12,7 @@ import ( "path/filepath" "strconv" "time" + "unicode/utf8" ) type NewGameInfo struct { @@ -26,6 +27,7 @@ type LockError struct { Message string `json:"message"` } +// CreateGame create a game entry to the database func CreateGame(w http.ResponseWriter, r *http.Request) { userId, err := userIdFromContext(r.Context()) if err != nil { @@ -55,6 +57,7 @@ func CreateGame(w http.ResponseWriter, r *http.Request) { ok(game, w, r) } +// GameInfoByID get the game save information from the database func GameInfoByID(w http.ResponseWriter, r *http.Request) { userId, err := userIdFromContext(r.Context()) if err != nil { @@ -78,6 +81,24 @@ func GameInfoByID(w http.ResponseWriter, r *http.Request) { ok(game, w, r) } +// AllGamesInformation all game saves information for a user +func AllGamesInformation(w http.ResponseWriter, r *http.Request) { + userId, err := userIdFromContext(r.Context()) + if err != nil { + internalServerError(w, r) + log.Println(err) + return + } + games, err := database.GameInfosByUserId(userId) + if err != nil { + internalServerError(w, r) + log.Println(err) + return + } + ok(games, w, r) +} + +// AskForUpload check if the game save is not lock, then lock it and generate a token func AskForUpload(w http.ResponseWriter, r *http.Request) { userId, err := userIdFromContext(r.Context()) if err != nil { @@ -106,6 +127,7 @@ func AskForUpload(w http.ResponseWriter, r *http.Request) { ok(token, w, r) } +// UploadSave upload the game save archive to the storage folder func UploadSave(w http.ResponseWriter, r *http.Request) { userId, err := userIdFromContext(r.Context()) if err != nil { @@ -120,6 +142,11 @@ func UploadSave(w http.ResponseWriter, r *http.Request) { return } defer database.UnlockGame(gameId) + hash := r.Header.Get("X-Game-Save-Hash") + if utf8.RuneCountInString(hash) == 0 { + badRequest("The header X-Game-Save-Hash is missing", w, r) + return + } game, err := database.GameInfoById(userId, gameId) if err != nil { internalServerError(w, r) @@ -139,7 +166,7 @@ func UploadSave(w http.ResponseWriter, r *http.Request) { log.Println(err) return } - err = database.UpdateGameRevision(game) + err = database.UpdateGameRevision(game, hash) if err != nil { internalServerError(w, r) log.Println(err) @@ -153,6 +180,7 @@ func UploadSave(w http.ResponseWriter, r *http.Request) { ok(payload, w, r) } +// Download send the game save archive to the client func Download(w http.ResponseWriter, r *http.Request) { userId, err := userIdFromContext(r.Context()) if err != nil { diff --git a/server/server.go b/server/server.go index def8336..c2dd51f 100644 --- a/server/server.go +++ b/server/server.go @@ -3,6 +3,7 @@ package server import ( "context" "errors" + "fmt" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "log" @@ -37,6 +38,7 @@ func Serve() { r.Route("/game", func(secureRouter chi.Router) { secureRouter.Use(authMiddleware) secureRouter.Post("/create", CreateGame) + secureRouter.Get("/all", AllGamesInformation) secureRouter.Get("/info/{id}", GameInfoByID) secureRouter.Post("/upload/init", AskForUpload) secureRouter.Group(func(uploadRouter chi.Router) { @@ -49,7 +51,7 @@ func Serve() { }) }) log.Println("Server is listening...") - err := http.ListenAndServe(":8080", router) + err := http.ListenAndServe(fmt.Sprintf(":%d", config.Server().Port), router) if err != nil { log.Fatal(err) }