First upload endpoint
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
config.yml
|
config.yml
|
||||||
cache/
|
cache/
|
||||||
opensavecloudserver
|
opensavecloudserver
|
||||||
|
storage/
|
||||||
|
.idea/
|
||||||
@@ -72,7 +72,7 @@ func Register(user *Registration) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return database.AddUser(user.Username, user.Firstname, user.Lastname, hash, user.Pronouns)
|
return database.AddUser(user.Username, hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
func token(userId int) (string, error) {
|
func token(userId int) (string, error) {
|
||||||
|
|||||||
@@ -6,4 +6,6 @@ database:
|
|||||||
username: root
|
username: root
|
||||||
features:
|
features:
|
||||||
allow_register: false
|
allow_register: false
|
||||||
cache: "/var/osc/cache"
|
path:
|
||||||
|
cache: "/var/osc/cache"
|
||||||
|
storage: "/var/osc/storage"
|
||||||
@@ -10,7 +10,12 @@ import (
|
|||||||
type Configuration struct {
|
type Configuration struct {
|
||||||
Database DatabaseConfiguration `yaml:"database"`
|
Database DatabaseConfiguration `yaml:"database"`
|
||||||
Features FeaturesConfiguration `yaml:"features"`
|
Features FeaturesConfiguration `yaml:"features"`
|
||||||
Cache string `yaml:"cache"`
|
Path PathConfiguration `yaml:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PathConfiguration struct {
|
||||||
|
Cache string `yaml:"cache"`
|
||||||
|
Storage string `yaml:"storage"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DatabaseConfiguration struct {
|
type DatabaseConfiguration struct {
|
||||||
@@ -47,6 +52,6 @@ func Features() *FeaturesConfiguration {
|
|||||||
return ¤tConfig.Features
|
return ¤tConfig.Features
|
||||||
}
|
}
|
||||||
|
|
||||||
func Cache() string {
|
func Path() *PathConfiguration {
|
||||||
return currentConfig.Cache
|
return ¤tConfig.Path
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,28 @@
|
|||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
"gorm.io/driver/mysql"
|
"gorm.io/driver/mysql"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"gorm.io/gorm/logger"
|
"gorm.io/gorm/logger"
|
||||||
"log"
|
"log"
|
||||||
"opensavecloudserver/config"
|
"opensavecloudserver/config"
|
||||||
"os"
|
"os"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
locks map[int]GameUploadToken
|
||||||
|
mu sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
var db *gorm.DB
|
var db *gorm.DB
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
locks = make(map[int]GameUploadToken)
|
||||||
dbConfig := config.Database()
|
dbConfig := config.Database()
|
||||||
var err error
|
var err error
|
||||||
db, err = gorm.Open(mysql.Open(
|
db, err = gorm.Open(mysql.Open(
|
||||||
@@ -36,8 +45,15 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Minute)
|
||||||
|
clearLocks()
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserByUsername get a user by the username
|
||||||
func UserByUsername(username string) (*User, error) {
|
func UserByUsername(username string) (*User, error) {
|
||||||
var user *User
|
var user *User
|
||||||
err := db.Model(User{}).Where(User{Username: username}).First(&user).Error
|
err := db.Model(User{}).Where(User{Username: username}).First(&user).Error
|
||||||
@@ -47,6 +63,7 @@ func UserByUsername(username string) (*User, error) {
|
|||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UserById get a user
|
||||||
func UserById(userId int) (*User, error) {
|
func UserById(userId int) (*User, error) {
|
||||||
var user *User
|
var user *User
|
||||||
err := db.Model(User{}).Where(User{ID: userId}).First(&user).Error
|
err := db.Model(User{}).Where(User{ID: userId}).First(&user).Error
|
||||||
@@ -56,6 +73,7 @@ func UserById(userId int) (*User, error) {
|
|||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddUser register a user
|
||||||
func AddUser(username string, password []byte) error {
|
func AddUser(username string, password []byte) error {
|
||||||
user := &User{
|
user := &User{
|
||||||
Username: username,
|
Username: username,
|
||||||
@@ -64,6 +82,7 @@ func AddUser(username string, password []byte) error {
|
|||||||
return db.Save(user).Error
|
return db.Save(user).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GameInfoById return information of a game
|
||||||
func GameInfoById(userId, gameId int) (*Game, error) {
|
func GameInfoById(userId, gameId int) (*Game, error) {
|
||||||
var game *Game
|
var game *Game
|
||||||
err := db.Model(Game{}).Where(Game{ID: gameId, UserId: userId}).First(&game).Error
|
err := db.Model(Game{}).Where(Game{ID: gameId, UserId: userId}).First(&game).Error
|
||||||
@@ -73,6 +92,7 @@ func GameInfoById(userId, gameId int) (*Game, error) {
|
|||||||
return game, nil
|
return game, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GameInfosByUserId get all saved games for a user
|
||||||
func GameInfosByUserId(userId int) ([]*Game, error) {
|
func GameInfosByUserId(userId int) ([]*Game, error) {
|
||||||
var games []*Game
|
var games []*Game
|
||||||
err := db.Model(Game{}).Where(Game{UserId: userId}).Find(&games).Error
|
err := db.Model(Game{}).Where(Game{UserId: userId}).Find(&games).Error
|
||||||
@@ -81,3 +101,53 @@ func GameInfosByUserId(userId int) ([]*Game, error) {
|
|||||||
}
|
}
|
||||||
return games, nil
|
return games, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateGame create an entry for a new game save, do this only for create a new entry
|
||||||
|
func CreateGame(userId int, name string) (*Game, error) {
|
||||||
|
gameUUID := uuid.New()
|
||||||
|
game := &Game{
|
||||||
|
Name: name,
|
||||||
|
Revision: 0,
|
||||||
|
PathStorage: gameUUID.String(),
|
||||||
|
UserId: userId,
|
||||||
|
Available: false,
|
||||||
|
}
|
||||||
|
if err := db.Save(&game).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return game, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AskForUpload Create a lock for upload a new revision of a game
|
||||||
|
func AskForUpload(userId, gameId int) (*GameUploadToken, error) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
_, err := GameInfoById(userId, gameId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if _, ok := locks[gameId]; !ok {
|
||||||
|
token := uuid.New()
|
||||||
|
return &GameUploadToken{
|
||||||
|
GameId: gameId,
|
||||||
|
UploadToken: token.String(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("game already locked")
|
||||||
|
}
|
||||||
|
|
||||||
|
// clearLocks clear lock of zombi upload
|
||||||
|
func clearLocks() {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
now := time.Now()
|
||||||
|
toUnlock := make([]int, 0)
|
||||||
|
for gameId, lock := range locks {
|
||||||
|
if lock.Expire.After(now) {
|
||||||
|
toUnlock = append(toUnlock, gameId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, gameId := range toUnlock {
|
||||||
|
delete(locks, gameId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,3 +18,9 @@ type Game struct {
|
|||||||
UserId int `json:"-"`
|
UserId int `json:"-"`
|
||||||
Available bool `json:"available"`
|
Available bool `json:"available"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GameUploadToken struct {
|
||||||
|
GameId int `json:"-"`
|
||||||
|
UploadToken string `json:"upload_token"`
|
||||||
|
Expire time.Time `json:"expire"`
|
||||||
|
}
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -5,6 +5,7 @@ go 1.18
|
|||||||
require (
|
require (
|
||||||
github.com/go-chi/chi/v5 v5.0.7
|
github.com/go-chi/chi/v5 v5.0.7
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible
|
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||||
|
github.com/google/uuid v1.3.0
|
||||||
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122
|
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122
|
||||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||||
gorm.io/driver/mysql v1.3.3
|
gorm.io/driver/mysql v1.3.3
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -4,6 +4,8 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC
|
|||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
|
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas=
|
||||||
|
|||||||
@@ -1 +1,76 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"opensavecloudserver/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NewGameInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadGameInfo struct {
|
||||||
|
GameId int `json:"game_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LockError struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateGame(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userId, err := userIdFromContext(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, r)
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gameInfo := new(NewGameInfo)
|
||||||
|
err = json.Unmarshal(body, gameInfo)
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, r)
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
game, err := database.CreateGame(userId, gameInfo.Name)
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, r)
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ok(game, w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AskForUpload(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userId, err := userIdFromContext(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, r)
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gameInfo := new(UploadGameInfo)
|
||||||
|
err = json.Unmarshal(body, gameInfo)
|
||||||
|
if err != nil {
|
||||||
|
internalServerError(w, r)
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
token, err := database.AskForUpload(userId, gameInfo.GameId)
|
||||||
|
if err != nil {
|
||||||
|
ok(LockError{Message: err.Error()}, w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ok(token, w, r)
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,16 +20,20 @@ func Serve() {
|
|||||||
router := chi.NewRouter()
|
router := chi.NewRouter()
|
||||||
router.Use(middleware.Logger)
|
router.Use(middleware.Logger)
|
||||||
router.Use(recovery)
|
router.Use(recovery)
|
||||||
router.Route("/api", func(r chi.Router) {
|
router.Route("/api", func(rApi chi.Router) {
|
||||||
r.Post("/login", Login)
|
rApi.Route("/v1", func(r chi.Router) {
|
||||||
if config.Features().AllowRegister {
|
r.Post("/login", Login)
|
||||||
r.Post("/register", Register)
|
if config.Features().AllowRegister {
|
||||||
}
|
r.Post("/register", Register)
|
||||||
r.Route("/system", func(systemRouter chi.Router) {
|
}
|
||||||
systemRouter.Get("/information", Information)
|
r.Route("/system", func(systemRouter chi.Router) {
|
||||||
})
|
systemRouter.Get("/information", Information)
|
||||||
r.Group(func(secureRouter chi.Router) {
|
})
|
||||||
secureRouter.Use(authMiddleware)
|
r.Group(func(secureRouter chi.Router) {
|
||||||
|
secureRouter.Use(authMiddleware)
|
||||||
|
secureRouter.Post("/game/create", CreateGame)
|
||||||
|
secureRouter.Post("/game/upload/init", AskForUpload)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
log.Println("Server is listening...")
|
log.Println("Server is listening...")
|
||||||
|
|||||||
Reference in New Issue
Block a user