322 lines
7.4 KiB
Go
322 lines
7.4 KiB
Go
package route
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"entgo.io/ent/dialect/sql"
|
|
"github.com/gorilla/mux"
|
|
"omctf.ru/block-game-backend/auth"
|
|
"omctf.ru/block-game-backend/codegen/ent"
|
|
"omctf.ru/block-game-backend/codegen/ent/level"
|
|
"omctf.ru/block-game-backend/db"
|
|
"omctf.ru/block-game-backend/game"
|
|
"omctf.ru/block-game-backend/messaging"
|
|
"omctf.ru/block-game-backend/schema"
|
|
"omctf.ru/block-game-backend/utils"
|
|
)
|
|
|
|
func CreateLevel(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
user, err := auth.GetUser(ctx)
|
|
if err != nil || user == nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
req, err := utils.GetJSONBody[struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Visibility string `json:"visibility"`
|
|
Data schema.LevelData `json:"data"`
|
|
Prize string `json:"prize"`
|
|
}](r)
|
|
if err != nil || req == nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if req.Data.Size > 20 || req.Data.Size <= 0 {
|
|
http.Error(w, "size must be between 1 and 20", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if len(req.Data.Tiles) > 256 {
|
|
http.Error(w, "max 256 tiles", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
level, err := db.Client.Level.CreateFrom(req).
|
|
SetOwner(user).
|
|
Save(ctx)
|
|
|
|
if ent.IsValidationError(err) {
|
|
http.Error(w, fmt.Sprintf("Invalid request: %s", err), http.StatusBadRequest)
|
|
return
|
|
}
|
|
if ent.IsConstraintError(err) {
|
|
http.Error(w, "Name taken", http.StatusConflict)
|
|
return
|
|
}
|
|
if err != nil || level == nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusCreated)
|
|
utils.RespondWithJSON(w, map[string]any{
|
|
"id": level.ID,
|
|
})
|
|
}
|
|
|
|
func getPublicLevelMetadata(level *ent.Level) map[string]any {
|
|
return map[string]any{
|
|
"id": level.ID,
|
|
"name": level.Name,
|
|
"description": level.Description,
|
|
"visibility": level.Visibility,
|
|
}
|
|
}
|
|
|
|
const (
|
|
PageSize = 20
|
|
)
|
|
|
|
func ListLevels(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
currentUser, err := auth.GetUser(ctx)
|
|
if err != nil || currentUser == nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
var resultLevels []*ent.Level
|
|
|
|
pageNumberStr := r.URL.Query().Get("page")
|
|
if pageNumberStr == "" {
|
|
// non-paged
|
|
resultLevels, err = db.Client.Level.Query().Where(
|
|
utils.LevelAccessibleBy(currentUser.ID),
|
|
).Order(level.ByCreatedAt(sql.OrderDesc())).All(ctx)
|
|
if err != nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
} else {
|
|
pageNumber, err := strconv.Atoi(pageNumberStr)
|
|
if err != nil || pageNumber < 0 {
|
|
http.Error(w, "invalid page number", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
resultLevels, err = db.Client.Level.Query().Where(
|
|
utils.LevelAccessibleBy(currentUser.ID),
|
|
).Order(level.ByCreatedAt(sql.OrderDesc())).Limit(PageSize).Offset(pageNumber * PageSize).All(ctx)
|
|
if err != nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
}
|
|
|
|
data := make([]any, 0, len(resultLevels))
|
|
for _, lvl := range resultLevels {
|
|
levelData := getPublicLevelMetadata(lvl)
|
|
data = append(data, levelData)
|
|
}
|
|
|
|
utils.RespondWithJSON(w, data)
|
|
}
|
|
|
|
func GetLevel(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
currentUser, err := auth.GetUser(ctx)
|
|
if err != nil || currentUser == nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
levelId := mux.Vars(r)["levelId"]
|
|
if levelId == "" {
|
|
http.Error(w, "levelId missing", http.StatusBadRequest)
|
|
return
|
|
}
|
|
levelIdInt, err := strconv.Atoi(levelId)
|
|
if err != nil {
|
|
http.Error(w, "levelId invalid", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
chosenLevel, err := db.Client.Level.Query().Where(
|
|
level.ID(levelIdInt),
|
|
utils.LevelAccessibleBy(currentUser.ID),
|
|
).WithOwner().Only(ctx)
|
|
if ent.IsNotFound(err) {
|
|
http.Error(w, "level not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
if err != nil || chosenLevel == nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
levelOwner, err := chosenLevel.Edges.OwnerOrErr()
|
|
if err != nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
privileged := levelOwner.ID == currentUser.ID
|
|
|
|
levelData := getPublicLevelMetadata(chosenLevel)
|
|
levelData["data"] = chosenLevel.Data
|
|
if privileged {
|
|
levelData["prize"] = chosenLevel.Prize
|
|
}
|
|
utils.RespondWithJSON(w, levelData)
|
|
}
|
|
|
|
func FindLevel(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
currentUser, err := auth.GetUser(ctx)
|
|
if err != nil || currentUser == nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
query := r.URL.Query().Get("name")
|
|
if query == "" {
|
|
http.Error(w, "query missing", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
foundLevel, err := db.Client.Level.Query().Where(
|
|
level.Name(query),
|
|
utils.LevelAccessibleBy(currentUser.ID),
|
|
).Only(ctx)
|
|
if ent.IsNotFound(err) {
|
|
http.Error(w, "level not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
if err != nil || foundLevel == nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
levelData := getPublicLevelMetadata(foundLevel)
|
|
utils.RespondWithJSON(w, levelData)
|
|
}
|
|
|
|
func InviteToLevel(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
currentUser, err := auth.GetUser(ctx)
|
|
if err != nil || currentUser == nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
req, err := utils.GetJSONBody[struct {
|
|
LevelId int `json:"level_id"`
|
|
UserId int `json:"user_id"`
|
|
}](r)
|
|
if err != nil || req == nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
levelToInvite, err := db.Client.Level.Query().Where(
|
|
level.ID(req.LevelId),
|
|
utils.LevelAccessibleBy(currentUser.ID),
|
|
).WithOwner().Only(ctx)
|
|
if ent.IsNotFound(err) {
|
|
http.Error(w, "level not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
if err != nil || levelToInvite == nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
levelOwner, err := levelToInvite.Edges.OwnerOrErr()
|
|
if err != nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
if levelOwner.ID != currentUser.ID {
|
|
http.Error(w, "only level owner can invite players", http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
userToInvite, err := db.Client.User.Get(ctx, req.UserId)
|
|
if ent.IsNotFound(err) {
|
|
http.Error(w, "user not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
if err != nil || userToInvite == nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
err = levelToInvite.Update().
|
|
AddInvitedPlayers(userToInvite).
|
|
Exec(ctx)
|
|
if err != nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func PlayLevel(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
|
|
currentUser, err := auth.GetUser(ctx)
|
|
if err != nil || currentUser == nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
levelId := mux.Vars(r)["levelId"]
|
|
if levelId == "" {
|
|
http.Error(w, "levelId missing", http.StatusBadRequest)
|
|
return
|
|
}
|
|
levelIdInt, err := strconv.Atoi(levelId)
|
|
if err != nil {
|
|
http.Error(w, "levelId invalid", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
chosenLevel, err := db.Client.Level.Query().Where(
|
|
level.ID(levelIdInt),
|
|
utils.LevelAccessibleBy(currentUser.ID),
|
|
).Only(ctx)
|
|
|
|
if ent.IsNotFound(err) {
|
|
http.Error(w, "level not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
if err != nil || chosenLevel == nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
conn, err := messaging.InitWebsocket(w, r)
|
|
if err != nil {
|
|
utils.BailInternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
session := &game.Session{
|
|
Conn: conn,
|
|
UserId: currentUser.ID,
|
|
LevelId: chosenLevel.ID,
|
|
Tiles: game.NewTiles(chosenLevel.Data.Size, chosenLevel.Data.Tiles),
|
|
}
|
|
|
|
session.Start()
|
|
}
|