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() }