ADD: added function to update db scheme automaticly
This commit is contained in:
@@ -55,7 +55,7 @@ func LoginHandler(c *gin.Context, db *sql.DB) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Create JWT token
|
// Create JWT token
|
||||||
token, err = CreateJWT(loggedInPlayer.ID, req.Email, "player", 60*time.Minute)
|
token, err = CreateJWT(loggedInPlayer.UUID, req.Email, "player", 60*time.Minute)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Token creation error"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Token creation error"})
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -39,22 +39,25 @@ func CheckIfTablesExist(db *sql.DB) (bool, error) {
|
|||||||
return true, nil // All tables exist
|
return true, nil // All tables exist
|
||||||
}
|
}
|
||||||
|
|
||||||
func tableExists(db *sql.DB, tableName string) (bool, error) {
|
// func tableExists(db *sql.DB, tableName string) (bool, error) {
|
||||||
query := `
|
// query := `
|
||||||
SELECT EXISTS (
|
// SELECT EXISTS (
|
||||||
SELECT FROM information_schema.tables
|
// SELECT FROM information_schema.tables
|
||||||
WHERE table_schema = 'public' AND table_name = $1
|
// WHERE table_schema = 'public' AND table_name = $1
|
||||||
);
|
// );
|
||||||
`
|
// `
|
||||||
var exists bool
|
// var exists bool
|
||||||
err := db.QueryRow(query, tableName).Scan(&exists)
|
// err := db.QueryRow(query, tableName).Scan(&exists)
|
||||||
return exists, err
|
// return exists, err
|
||||||
}
|
// }
|
||||||
|
|
||||||
func InitTables(d *sql.DB) error {
|
func InitTables(d *sql.DB) error {
|
||||||
|
CreateOrUpdateTablePG(d, "users", player.User{})
|
||||||
|
CreateOrUpdateTablePG(d, "roles", player.Roles{})
|
||||||
|
|
||||||
tables := []string{
|
tables := []string{
|
||||||
player.PlayerTable,
|
// player.PlayerTable,
|
||||||
player.RoleTable,
|
// player.RoleTable,
|
||||||
teamTable,
|
teamTable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
188
backend/internal/database/migrateScheme.go
Normal file
188
backend/internal/database/migrateScheme.go
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Auto-create table, columns, types, indexes for PostgreSQL
|
||||||
|
func CreateOrUpdateTablePG(db *sql.DB, table string, model interface{}) error {
|
||||||
|
// 0) Check if table exists
|
||||||
|
exists, err := tableExists(db, table)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
// Create table from model
|
||||||
|
if err := createTable(db, table, model); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update table if it exists
|
||||||
|
return alterTable(db, table, model)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tableExists(db *sql.DB, table string) (bool, error) {
|
||||||
|
query := `SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name=$1);`
|
||||||
|
var exists bool
|
||||||
|
err := db.QueryRow(query, table).Scan(&exists)
|
||||||
|
return exists, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTable(db *sql.DB, table string, model interface{}) error {
|
||||||
|
t := reflect.TypeOf(model)
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
return fmt.Errorf("model must be a struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
cols := []string{}
|
||||||
|
idx := []string{}
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
|
||||||
|
// ignore fields not meant for DB
|
||||||
|
if f.Tag.Get("ignore") == "true" || f.Tag.Get("db") == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
col := f.Tag.Get("db")
|
||||||
|
if col == "" {
|
||||||
|
col = strings.ToLower(f.Name)
|
||||||
|
}
|
||||||
|
sqlType := f.Tag.Get("sql")
|
||||||
|
if sqlType == "" {
|
||||||
|
return fmt.Errorf("field '%s' missing sql tag", f.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
cols = append(cols, fmt.Sprintf("\"%s\" %s", col, sqlType))
|
||||||
|
|
||||||
|
if f.Tag.Get("index") == "true" {
|
||||||
|
idx = append(idx, col)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf(`CREATE TABLE "%s" (%s);`, table, strings.Join(cols, ", "))
|
||||||
|
log.Println("[CREATE TABLE]", query)
|
||||||
|
|
||||||
|
if _, err := db.Exec(query); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create indexes
|
||||||
|
for _, col := range idx {
|
||||||
|
indexName := fmt.Sprintf("%s_%s_idx", table, col)
|
||||||
|
iq := fmt.Sprintf(`CREATE INDEX "%s" ON "%s" ("%s");`, indexName, table, col)
|
||||||
|
log.Println("[CREATE INDEX]", iq)
|
||||||
|
if _, err := db.Exec(iq); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func alterTable(db *sql.DB, table string, model interface{}) error {
|
||||||
|
currentCols, err := getColumnsPG(db, table)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
currentIdx, err := getIndexesPG(db, table)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
t := reflect.TypeOf(model)
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
col := f.Tag.Get("db")
|
||||||
|
if col == "" {
|
||||||
|
col = strings.ToLower(f.Name)
|
||||||
|
}
|
||||||
|
sqlType := f.Tag.Get("sql")
|
||||||
|
|
||||||
|
// Missing column
|
||||||
|
if _, ok := currentCols[col]; !ok {
|
||||||
|
query := fmt.Sprintf(`ALTER TABLE "%s" ADD COLUMN "%s" %s;`, table, col, sqlType)
|
||||||
|
log.Println("[ADD COLUMN]", query)
|
||||||
|
if _, err := db.Exec(query); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type mismatch → alter
|
||||||
|
pgType := strings.ToLower(currentCols[col])
|
||||||
|
expType := strings.ToLower(sqlType)
|
||||||
|
|
||||||
|
if !strings.Contains(pgType, strings.Split(expType, "(")[0]) {
|
||||||
|
query := fmt.Sprintf(`ALTER TABLE "%s" ALTER COLUMN "%s" TYPE %s;`, table, col, sqlType)
|
||||||
|
log.Println("[ALTER TYPE]", query)
|
||||||
|
if _, err := db.Exec(query); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indexing
|
||||||
|
if f.Tag.Get("index") == "true" {
|
||||||
|
idxName := fmt.Sprintf("%s_%s_idx", table, col)
|
||||||
|
|
||||||
|
if !currentIdx[idxName] {
|
||||||
|
iq := fmt.Sprintf(`CREATE INDEX "%s" ON "%s" ("%s");`, idxName, table, col)
|
||||||
|
log.Println("[CREATE INDEX]", iq)
|
||||||
|
if _, err := db.Exec(iq); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
func getColumnsPG(db *sql.DB, table string) (map[string]string, error) {
|
||||||
|
q := `SELECT column_name, data_type FROM information_schema.columns WHERE table_name=$1;`
|
||||||
|
rows, err := db.Query(q, table)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
cols := map[string]string{}
|
||||||
|
for rows.Next() {
|
||||||
|
var name, dtype string
|
||||||
|
if err := rows.Scan(&name, &dtype); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cols[name] = dtype
|
||||||
|
}
|
||||||
|
return cols, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getIndexesPG(db *sql.DB, table string) (map[string]bool, error) {
|
||||||
|
q := `SELECT indexname FROM pg_indexes WHERE tablename=$1;`
|
||||||
|
rows, err := db.Query(q, table)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
idx := map[string]bool{}
|
||||||
|
for rows.Next() {
|
||||||
|
var i string
|
||||||
|
if err := rows.Scan(&i); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
idx[i] = true
|
||||||
|
}
|
||||||
|
return idx, nil
|
||||||
|
}
|
||||||
@@ -32,35 +32,36 @@ func GetPlayers(c *gin.Context, db *sql.DB) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CreatePlayer(c *gin.Context, db *sql.DB) {
|
func CreatePlayer(c *gin.Context, db *sql.DB) {
|
||||||
var newPlayer Player
|
var newPlayer User
|
||||||
var err error
|
var err error
|
||||||
if err := c.ShouldBindJSON(&newPlayer); err != nil {
|
if err := c.ShouldBindJSON(&newPlayer); err != nil {
|
||||||
log.Printf("Error binding player data: %v", err)
|
log.Printf("Error binding player data: %v", err)
|
||||||
common.RespondError(c, http.StatusBadRequest, "Invalid player data")
|
common.RespondError(c, http.StatusBadRequest, "Invalid player data")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
newPlayer.Password, err = common.HashPassword(newPlayer.Password)
|
newPlayer.password, err = common.HashPassword(newPlayer.password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error hashing password: %v", err)
|
log.Printf("Error hashing password: %v", err)
|
||||||
common.RespondError(c, http.StatusInternalServerError, "Failed to hash password")
|
common.RespondError(c, http.StatusInternalServerError, "Failed to hash password")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newPlayer.ID = uuid.New().String()
|
newPlayer.UUID = uuid.New().String()
|
||||||
err = savePlayer(db, newPlayer)
|
err = savePlayer(db, newPlayer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error saving player: %v", err)
|
log.Printf("Error saving player: %v", err)
|
||||||
common.RespondError(c, http.StatusInternalServerError, "Failed to create player")
|
common.RespondError(c, http.StatusInternalServerError, "Failed to create player")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = AddRoleToPlayer(db, newPlayer.ID, "player")
|
var roles = []string{"player"}
|
||||||
|
err = AddRoleToPlayer(db, newPlayer.UUID, roles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error adding role to player: %v", err)
|
log.Printf("Error adding role to player: %v", err)
|
||||||
common.RespondError(c, http.StatusInternalServerError, "Failed to assign role to player")
|
common.RespondError(c, http.StatusInternalServerError, "Failed to assign role to player")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("User %s (%s) created player: %s", c.GetString("userId"), c.GetString("email"), newPlayer.Name)
|
log.Printf("User %s (%s) created player: %s", c.GetString("userId"), c.GetString("email"), newPlayer.Username)
|
||||||
|
|
||||||
common.RespondCreated(c, newPlayer)
|
common.RespondCreated(c, newPlayer)
|
||||||
}
|
}
|
||||||
@@ -80,19 +81,19 @@ func GetPlayer(c *gin.Context, db *sql.DB, id string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if player.ID == "" {
|
if player.UUID == "" {
|
||||||
log.Printf("Player with ID %s not found", playerID)
|
log.Printf("Player with ID %s not found", playerID)
|
||||||
common.RespondError(c, http.StatusNotFound, "Player not found")
|
common.RespondError(c, http.StatusNotFound, "Player not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
role, err := GetPlayerRole(db, player.ID)
|
role, err := GetPlayerRole(db, player.UUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error retrieving role for player ID %s: %v", player.ID, err)
|
log.Printf("Error retrieving role for player ID %s: %v", player.UUID, err)
|
||||||
common.RespondError(c, http.StatusInternalServerError, "Failed to retrieve player role")
|
common.RespondError(c, http.StatusInternalServerError, "Failed to retrieve player role")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
player.Role = role
|
player.Role = role
|
||||||
log.Printf(player.ID, player.Name, player.Email, player.Role)
|
log.Printf(player.UUID, player.Username, player.Email, player.Role)
|
||||||
c.JSON(http.StatusOK, player)
|
c.JSON(http.StatusOK, player)
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -109,14 +110,14 @@ func UpdatePlayer(c *gin.Context, db *sql.DB) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var updatedPlayer Player
|
var updatedPlayer User
|
||||||
if err := c.ShouldBindJSON(&updatedPlayer); err != nil {
|
if err := c.ShouldBindJSON(&updatedPlayer); err != nil {
|
||||||
log.Printf("Error binding player data: %v", err)
|
log.Printf("Error binding player data: %v", err)
|
||||||
common.RespondError(c, http.StatusBadRequest, "Invalid player data")
|
common.RespondError(c, http.StatusBadRequest, "Invalid player data")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedPlayer.ID = playerID
|
updatedPlayer.UUID = playerID
|
||||||
err := updatePlayer(db, updatedPlayer)
|
err := updatePlayer(db, updatedPlayer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error updating player with ID %s: %v", playerID, err)
|
log.Printf("Error updating player with ID %s: %v", playerID, err)
|
||||||
@@ -124,7 +125,7 @@ func UpdatePlayer(c *gin.Context, db *sql.DB) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("User %s (%s) updated player: %s", c.GetString("userId"), c.GetString("email"), updatedPlayer.Name)
|
log.Printf("User %s (%s) updated player: %s", c.GetString("userId"), c.GetString("email"), updatedPlayer.Username)
|
||||||
|
|
||||||
c.JSON(http.StatusOK, updatedPlayer)
|
c.JSON(http.StatusOK, updatedPlayer)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,31 +3,66 @@ package player
|
|||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
const PlayerTable = `
|
type User struct {
|
||||||
CREATE TABLE IF NOT EXISTS players (
|
UUID string `db:"uuid" sql:"VARCHAR(255)" index:"true"`
|
||||||
id uuid PRIMARY KEY,
|
Username string `db:"username" sql:"VARCHAR(100)"`
|
||||||
name VARCHAR(100) NOT NULL,
|
Email string `db:"email" sql:"VARCHAR(255)" index:"true"`
|
||||||
email VARCHAR(100) NOT NULL UNIQUE,
|
lastname string `db:"lastname" sql:"VARCHAR(100)"`
|
||||||
password_hash VARCHAR(255) NOT NULL
|
fistname string `db:"fistname" sql:"VARCHAR(100)"`
|
||||||
);
|
password string `db:"password_hash" sql:"VARCHAR(255)"`
|
||||||
`
|
phone string `db:"phone" sql:"VARCHAR(20)"`
|
||||||
const RoleTable = `
|
avatarURL string `db:"avatar_url" sql:"VARCHAR(255)"`
|
||||||
CREATE TABLE IF NOT EXISTS roles (
|
IsActive bool `db:"is_active" sql:"BOOLEAN"`
|
||||||
id SERIAL PRIMARY KEY,
|
birthday time.Time `db:"birthday" sql:"DATE"`
|
||||||
player_id uuid NOT NULL,
|
createdAt time.Time `db:"created_at" sql:"TIMESTAMP"`
|
||||||
role VARCHAR(50) NOT NULL CHECK (role IN ('player', 'admin')),
|
updatedAt time.Time `db:"updated_at" sql:"TIMESTAMP"`
|
||||||
FOREIGN KEY (player_id) REFERENCES players(id) ON DELETE CASCADE
|
LastLogin time.Time `db:"last_login" sql:"TIMESTAMP"`
|
||||||
);
|
Role []string `ignore:"true"` // wird NICHT in der DB angelegt
|
||||||
`
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type Roles struct {
|
||||||
|
ID int `db:"id" sql:"SERIAL PRIMARY KEY"`
|
||||||
|
PlayerID string `db:"player_id" sql:"UUID"`
|
||||||
|
Role []string `db:"role" sql:"TEXT[]"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// const PlayerTable = `
|
||||||
|
// CREATE TABLE IF NOT EXISTS users (
|
||||||
|
// id uuid PRIMARY KEY,
|
||||||
|
// username VARCHAR(100) NOT NULL,
|
||||||
|
// lastname VARCHAR(100) NOT NULL,
|
||||||
|
// fistname VARCHAR(100) NOT NULL,
|
||||||
|
// email VARCHAR(100) NOT NULL UNIQUE,
|
||||||
|
// password_hash VARCHAR(255) NOT NULL,
|
||||||
|
// created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
// updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
// last_login TIMESTAMP,
|
||||||
|
// phone VARCHAR(20),
|
||||||
|
// avatar_url VARCHAR(255),
|
||||||
|
// is_active BOOLEAN NOT NULL DEFAULT TRUE
|
||||||
|
// );
|
||||||
|
// `
|
||||||
|
|
||||||
|
// const RoleTable = `
|
||||||
|
// CREATE TABLE IF NOT EXISTS roles (
|
||||||
|
// id SERIAL PRIMARY KEY,
|
||||||
|
// player_id uuid NOT NULL,
|
||||||
|
// role VARCHAR(50) NOT NULL CHECK (role IN ('player', 'admin')),
|
||||||
|
// FOREIGN KEY (player_id) REFERENCES users(id) ON DELETE CASCADE
|
||||||
|
// );
|
||||||
|
//`
|
||||||
|
|
||||||
type Team struct {
|
type Team struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Players []Player `json:"players"` // Players in the team
|
Players []User `json:"users"` // Players in the team
|
||||||
// Matches []Match `json:"matches"` // Matches the team is involved in
|
// Matches []Match `json:"matches"` // Matches the team is involved in
|
||||||
MatchesWon int `json:"matchesWon"` // Number of matches won by the team
|
MatchesWon int `json:"matchesWon"` // Number of matches won by the team
|
||||||
MatchesLost int `json:"matchesLost"` // Number of matches lost by the team
|
MatchesLost int `json:"matchesLost"` // Number of matches lost by the team
|
||||||
@@ -36,22 +71,22 @@ type Team struct {
|
|||||||
MatchesPlayedCount int `json:"matchesPlayedCount"` // Total matches played by the team
|
MatchesPlayedCount int `json:"matchesPlayedCount"` // Total matches played by the team
|
||||||
}
|
}
|
||||||
|
|
||||||
type Player struct {
|
// type Player struct {
|
||||||
ID string `json:"id"`
|
// ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
// Name string `json:"name"`
|
||||||
Email string `json:"email"`
|
// Email string `json:"email"`
|
||||||
Password string `json:"password,omitempty"` // Password is optional for responses
|
// Password string `json:"password,omitempty"` // Password is optional for responses
|
||||||
Role string `json:"role"` // e.g., "player","organizer"
|
// Role string `json:"role"` // e.g., "player","organizer"
|
||||||
Teams []Team `json:"teams"` // Teams the player is part of
|
// Teams []Team `json:"teams"` // Teams the player is part of
|
||||||
// Tournaments []Tournament `json:"tournaments"` // Tournaments the player is registered in
|
// // Tournaments []Tournament `json:"tournaments"` // Tournaments the player is registered in
|
||||||
OwnedTournaments []string `json:"ownedTournaments"` // Tournaments the player is organizing
|
// OwnedTournaments []string `json:"ownedTournaments"` // Tournaments the player is organizing
|
||||||
ParticipatedTournaments []string `json:"participatedTournaments"` // Tournaments the player is participating in
|
// ParticipatedTournaments []string `json:"participatedTournaments"` // Tournaments the player is participating in
|
||||||
}
|
// }
|
||||||
type PlayerListResponse struct {
|
type PlayerListResponse struct {
|
||||||
Players []Player `json:"players"`
|
Players []User `json:"users"`
|
||||||
}
|
}
|
||||||
type PlayerResponse struct {
|
type PlayerResponse struct {
|
||||||
Player Player `json:"player"`
|
Player User `json:"player"`
|
||||||
}
|
}
|
||||||
type CreatePlayerRequest struct {
|
type CreatePlayerRequest struct {
|
||||||
Name string `json:"name" binding:"required"`
|
Name string `json:"name" binding:"required"`
|
||||||
@@ -68,75 +103,79 @@ type DeletePlayerRequest struct {
|
|||||||
ID string `json:"id" binding:"required"`
|
ID string `json:"id" binding:"required"`
|
||||||
}
|
}
|
||||||
type PlayerService interface {
|
type PlayerService interface {
|
||||||
CreatePlayer(req CreatePlayerRequest) (Player, error)
|
CreatePlayer(req CreatePlayerRequest) (User, error)
|
||||||
GetPlayer(id string) (Player, error)
|
GetPlayer(id string) (User, error)
|
||||||
UpdatePlayer(id string, req UpdatePlayerRequest) (Player, error)
|
UpdatePlayer(id string, req UpdatePlayerRequest) (User, error)
|
||||||
DeletePlayer(id string) error
|
DeletePlayer(id string) error
|
||||||
ListPlayers() ([]Player, error)
|
ListPlayers() ([]User, error)
|
||||||
GetPlayerTeams(id string) ([]Team, error)
|
GetPlayerTeams(id string) ([]Team, error)
|
||||||
GetPlayerByEmail(email string) (Player, error)
|
GetPlayerByEmail(email string) (User, error)
|
||||||
GetPlayerByName(name string) (Player, error)
|
GetPlayerByName(name string) (User, error)
|
||||||
GetPlayerByID(id string) (Player, error)
|
GetPlayerByID(id string) (User, error)
|
||||||
GetPlayerByRole(role string) ([]Player, error)
|
GetPlayerByRole(role string) ([]User, error)
|
||||||
GetPlayerByTeam(teamID string) ([]Player, error)
|
GetPlayerByTeam(teamID string) ([]User, error)
|
||||||
GetPlayerByTournament(tournamentID string) ([]Player, error)
|
GetPlayerByTournament(tournamentID string) ([]User, error)
|
||||||
GetPlayerByOrganizer(organizerID string) ([]Player, error)
|
GetPlayerByOrganizer(organizerID string) ([]User, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoginPlayer(db *sql.DB, email, password string) (Player, error) {
|
func LoginPlayer(db *sql.DB, email, password string) (User, error) {
|
||||||
var player Player
|
var player User
|
||||||
var playerPassword string
|
var playerPassword string
|
||||||
err := db.QueryRow("SELECT id, name, email, password FROM players WHERE email = $1", email).Scan(&player.ID, &player.Name, &player.Email, playerPassword)
|
err := db.QueryRow("SELECT id, name, email, password FROM users WHERE email = $1", email).Scan(&player.UUID, &player.Username, &player.Email, playerPassword)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error logging in player with email %s: %v", email, err)
|
log.Printf("Error logging in player with email %s: %v", email, err)
|
||||||
return Player{}, err
|
return User{}, err
|
||||||
}
|
}
|
||||||
if player.ID == "" {
|
if player.UUID == "" {
|
||||||
log.Printf("Player with email %s not found", email)
|
log.Printf("User with email %s not found", email)
|
||||||
return Player{}, sql.ErrNoRows // or a custom error
|
return User{}, sql.ErrNoRows // or a custom error
|
||||||
}
|
}
|
||||||
// Check if the password matches
|
// Check if the password matches
|
||||||
if password == playerPassword {
|
if password == playerPassword {
|
||||||
log.Printf("Player %s logged in successfully", player.Name)
|
log.Printf("User %s logged in successfully", player.Username)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Player %s logged in successfully", player.Name)
|
log.Printf("User %s logged in successfully", player.Username)
|
||||||
return player, nil
|
return player, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPlayerRole(db *sql.DB, playerID string) (string, error) {
|
func GetPlayerRole(db *sql.DB, playerID string) ([]string, error) {
|
||||||
var role string
|
var role []string
|
||||||
err := db.QueryRow("SELECT role FROM roles WHERE player_id = $1", playerID).Scan(&role)
|
var tmp string
|
||||||
|
|
||||||
|
err := db.QueryRow("SELECT role FROM roles WHERE player_id = $1", playerID).Scan(&tmp)
|
||||||
|
log.Println(tmp)
|
||||||
|
role = strings.Split(tmp, ",")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error retrieving role for player ID %s: %v", playerID, err)
|
log.Printf("Error retrieving role for player ID %s: %v", playerID, err)
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
if role == "" {
|
if len(role) == 0 {
|
||||||
log.Printf("No role found for player ID %s", playerID)
|
log.Printf("No role found for player ID %s", playerID)
|
||||||
return "", sql.ErrNoRows // or a custom error
|
return nil, sql.ErrNoRows // or a custom error
|
||||||
}
|
}
|
||||||
log.Printf("Retrieved role for player ID %s: %s", playerID, role)
|
log.Printf("Retrieved role for player ID %s: %s", playerID, role)
|
||||||
return role, nil
|
return role, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func savePlayer(db *sql.DB, player Player) error {
|
func savePlayer(db *sql.DB, player User) error {
|
||||||
|
|
||||||
// Ensure the player ID is set
|
// Ensure the player ID is set
|
||||||
if player.ID == "" {
|
if player.UUID == "" {
|
||||||
player.ID = uuid.New().String() // Generate a new UUID if not set
|
player.UUID = uuid.New().String() // Generate a new UUID if not set
|
||||||
log.Printf("Generated new player ID: %s", player.ID)
|
log.Printf("Generated new player ID: %s", player.UUID)
|
||||||
}
|
}
|
||||||
// Insert the player into the database
|
// Insert the player into the database
|
||||||
if player.Role == "" {
|
if len(player.Role) == 0 {
|
||||||
player.Role = "player" // Default role if not specified
|
player.Role = append(player.Role, "player") // Default role if not specified
|
||||||
log.Printf("Setting default role for player %s to 'player'", player.Name)
|
log.Printf("Setting default role for player %s to 'player'", player.Username)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Saving player: ID=%v, Name=%v, Email=%v", player.ID, player.Name, player.Email)
|
log.Printf("Saving player: ID=%v, Name=%v, Email=%v", player.UUID, player.Username, player.Email)
|
||||||
|
|
||||||
stmt := "INSERT INTO public.players (id, name, email,password_hash) VALUES ($1, $2, $3,$4)"
|
stmt := "INSERT INTO public.users (id, name, email,password_hash) VALUES ($1, $2, $3,$4)"
|
||||||
log.Printf("Generated SQL statement: %s", stmt)
|
log.Printf("Generated SQL statement: %s", stmt)
|
||||||
_, err := db.Exec(stmt, player.ID, player.Name, player.Email, player.Password)
|
_, err := db.Exec(stmt, player.UUID, player.Username, player.Email, player.password)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error saving player to database: %v", err)
|
log.Printf("Error saving player to database: %v", err)
|
||||||
@@ -147,17 +186,17 @@ func savePlayer(db *sql.DB, player Player) error {
|
|||||||
// } else {
|
// } else {
|
||||||
// log.Printf("Last insert ID: %d", lastID)
|
// log.Printf("Last insert ID: %d", lastID)
|
||||||
// }
|
// }
|
||||||
// if err := db.QueryRow("SELECT id, name, email FROM players WHERE id = ?", player.ID).Scan(&player.ID, &player.Name, &player.Email, &player.Role); err != nil {
|
// if err := db.QueryRow("SELECT id, name, email FROM users WHERE id = ?", player.ID).Scan(&player.ID, &player.Name, &player.Email, &player.Role); err != nil {
|
||||||
// log.Printf("Error retrieving player from database: %v", err)
|
// log.Printf("Error retrieving player from database: %v", err)
|
||||||
// return err
|
// return err
|
||||||
// }
|
// }
|
||||||
log.Printf("Player %s saved to database", player.Name)
|
log.Printf("User %s saved to database", player.Username)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddRoleToPlayer(db *sql.DB, playerID, role string) error {
|
func AddRoleToPlayer(db *sql.DB, playerID string, role []string) error {
|
||||||
if playerID == "" {
|
if playerID == "" {
|
||||||
log.Printf("Player ID is required to add a role, but got empty ID")
|
log.Printf("User ID is required to add a role, but got empty ID")
|
||||||
return sql.ErrNoRows // or a custom error
|
return sql.ErrNoRows // or a custom error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,22 +213,22 @@ func AddRoleToPlayer(db *sql.DB, playerID, role string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetAllPlayers(db *sql.DB) ([]Player, error) {
|
func GetAllPlayers(db *sql.DB) ([]User, error) {
|
||||||
rows, err := db.Query("SELECT id, name, email FROM public.players")
|
rows, err := db.Query("SELECT id, name, email FROM public.users")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error retrieving players: %v", err)
|
log.Printf("Error retrieving users: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
var players []Player
|
var users []User
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var player Player
|
var player User
|
||||||
if err := rows.Scan(&player.ID, &player.Name, &player.Email); err != nil {
|
if err := rows.Scan(&player.UUID, &player.Username, &player.Email); err != nil {
|
||||||
log.Printf("Error scanning player row: %v", err)
|
log.Printf("Error scanning player row: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
players = append(players, player)
|
users = append(users, player)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := rows.Err(); err != nil {
|
if err := rows.Err(); err != nil {
|
||||||
@@ -197,33 +236,33 @@ func GetAllPlayers(db *sql.DB) ([]Player, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return players, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPlayerByID(db *sql.DB, id string) (Player, error) {
|
func GetPlayerByID(db *sql.DB, id string) (User, error) {
|
||||||
var player Player
|
var player User
|
||||||
err := db.QueryRow("SELECT id, name, email FROM players WHERE id = $1", id).Scan(&player.ID, &player.Name, &player.Email)
|
err := db.QueryRow("SELECT id, name, email FROM users WHERE id = $1", id).Scan(&player.UUID, &player.Username, &player.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error retrieving player by ID %s: %v", id, err)
|
log.Printf("Error retrieving player by ID %s: %v", id, err)
|
||||||
return Player{}, err
|
return User{}, err
|
||||||
}
|
}
|
||||||
return player, nil
|
return player, nil
|
||||||
}
|
}
|
||||||
func GetPlayerByEmail(db *sql.DB, email string) (Player, error) {
|
func GetPlayerByEmail(db *sql.DB, email string) (User, error) {
|
||||||
var player Player
|
var player User
|
||||||
err := db.QueryRow("SELECT id, name, email FROM players WHERE email = $1", email).Scan(&player.ID, &player.Name, &player.Email, &player.Role)
|
err := db.QueryRow("SELECT id, name, email FROM users WHERE email = $1", email).Scan(&player.UUID, &player.Username, &player.Email, &player.Role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error retrieving player by email %s: %v", email, err)
|
log.Printf("Error retrieving player by email %s: %v", email, err)
|
||||||
return Player{}, err
|
return User{}, err
|
||||||
}
|
}
|
||||||
return player, nil
|
return player, nil
|
||||||
}
|
}
|
||||||
func GetPlayerByName(db *sql.DB, name string) (Player, error) {
|
func GetPlayerByName(db *sql.DB, name string) (User, error) {
|
||||||
var player Player
|
var player User
|
||||||
err := db.QueryRow("SELECT id, name, email FROM players WHERE name = $1", name).Scan(&player.ID, &player.Name, &player.Email, &player.Role)
|
err := db.QueryRow("SELECT id, name, email FROM users WHERE name = $1", name).Scan(&player.UUID, &player.Username, &player.Email, &player.Role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error retrieving player by name %s: %v", name, err)
|
log.Printf("Error retrieving player by name %s: %v", name, err)
|
||||||
return Player{}, err
|
return User{}, err
|
||||||
}
|
}
|
||||||
return player, nil
|
return player, nil
|
||||||
}
|
}
|
||||||
@@ -232,55 +271,33 @@ func GetPlayerByName(db *sql.DB, name string) (Player, error) {
|
|||||||
func deletePlayer(db *sql.DB, id string) error {
|
func deletePlayer(db *sql.DB, id string) error {
|
||||||
// Delete the player from the database
|
// Delete the player from the database
|
||||||
log.Printf("Deleting player with ID: %s", id)
|
log.Printf("Deleting player with ID: %s", id)
|
||||||
_, err := db.Exec("DELETE FROM public.players WHERE id = $1", id)
|
_, err := db.Exec("DELETE FROM public.users WHERE id = $1", id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error deleting player with ID %s: %v", id, err)
|
log.Printf("Error deleting player with ID %s: %v", id, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Printf("Player with ID %s deleted successfully", id)
|
log.Printf("User with ID %s deleted successfully", id)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePlayer updates an existing player in the database.
|
// UpdatePlayer updates an existing player in the database.
|
||||||
// It requires the player ID to be set in the Player struct.
|
// It requires the player ID to be set in the User struct.
|
||||||
func updatePlayer(db *sql.DB, player Player) error {
|
func updatePlayer(db *sql.DB, player User) error {
|
||||||
// Ensure the player ID is set
|
// Ensure the player ID is set
|
||||||
if player.ID == "" {
|
if player.UUID == "" {
|
||||||
log.Printf("Player ID is required for update, but got empty ID")
|
log.Printf("User ID is required for update, but got empty ID")
|
||||||
return sql.ErrNoRows // or a custom error
|
return sql.ErrNoRows // or a custom error
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Updating player: ID=%v, Name=%v, Email=%v", player.ID, player.Name, player.Email)
|
log.Printf("Updating player: ID=%v, Name=%v, Email=%v", player.UUID, player.Username, player.Email)
|
||||||
|
|
||||||
stmt := "UPDATE public.players SET name = $1, email = $2 WHERE id = $3"
|
stmt := "UPDATE public.users SET name = $1, email = $2 WHERE id = $3"
|
||||||
_, err := db.Exec(stmt, player.Name, player.Email, player.ID)
|
_, err := db.Exec(stmt, player.Username, player.Email, player.UUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error updating player in database: %v", err)
|
log.Printf("Error updating player in database: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("Player %s updated successfully", player.Name)
|
log.Printf("User %s updated successfully", player.Username)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var players_default = []Player{{
|
|
||||||
ID: "1",
|
|
||||||
Name: "John Doe",
|
|
||||||
Email: "John.Doe@example.de",
|
|
||||||
Role: "player",
|
|
||||||
Teams: []Team{
|
|
||||||
{ID: "team1"},
|
|
||||||
{ID: "team2"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ID: "2",
|
|
||||||
Name: "Jane Smith",
|
|
||||||
Email: "Jane-Smith@example.de",
|
|
||||||
Role: "player",
|
|
||||||
Teams: []Team{
|
|
||||||
{ID: "team3"},
|
|
||||||
{ID: "team1"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import TeamManagement from './pages/Teams';
|
|||||||
import ViewEditPlayer from './pages/ViewEditPlayer';
|
import ViewEditPlayer from './pages/ViewEditPlayer';
|
||||||
import Tournaments from './pages/Tournaments';
|
import Tournaments from './pages/Tournaments';
|
||||||
import NewTournament from './pages/NewTournament';
|
import NewTournament from './pages/NewTournament';
|
||||||
|
import Administration from './pages/Administration';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ function App() {
|
|||||||
<Route path="/players/:id" element={<ViewEditPlayer />} />
|
<Route path="/players/:id" element={<ViewEditPlayer />} />
|
||||||
|
|
||||||
<Route path="/teams" element={<ProtectedRoute><TeamManagement /></ProtectedRoute>} />
|
<Route path="/teams" element={<ProtectedRoute><TeamManagement /></ProtectedRoute>} />
|
||||||
|
<Route path="/Administration" element={<ProtectedRoute><Administration /></ProtectedRoute>} />
|
||||||
|
|
||||||
<Route path="/tournament/:id" element={<ProtectedRoute><TournamentDetails /></ProtectedRoute>} />
|
<Route path="/tournament/:id" element={<ProtectedRoute><TournamentDetails /></ProtectedRoute>} />
|
||||||
<Route path="/tournament/new" element={<ProtectedRoute><NewTournament /></ProtectedRoute>} />
|
<Route path="/tournament/new" element={<ProtectedRoute><NewTournament /></ProtectedRoute>} />
|
||||||
|
|||||||
60
frontend/src/components/interfaces/users.tsx
Normal file
60
frontend/src/components/interfaces/users.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Interfaces and enums for user-related types
|
||||||
|
* File: /home/henry/code/Volleyball/frontend/src/components/interfaces/users.tsx
|
||||||
|
*/
|
||||||
|
|
||||||
|
export enum UserRole {
|
||||||
|
Admin = 'admin',
|
||||||
|
Coach = 'coach',
|
||||||
|
Player = 'player',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** Primary user model returned from the API */
|
||||||
|
export interface User {
|
||||||
|
UUID: string
|
||||||
|
Username: string
|
||||||
|
Email: string
|
||||||
|
FirstName?: string
|
||||||
|
LastName?: string
|
||||||
|
AvatarUrl?: string
|
||||||
|
Roles: UserRole[]
|
||||||
|
IsActive: boolean
|
||||||
|
Phone?: string
|
||||||
|
TeamId?: string[]
|
||||||
|
CreatedAt?: string // ISO date
|
||||||
|
UpdatedAt?: string // ISO date
|
||||||
|
LastLogin?: string // ISO date
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Data required to create a new user */
|
||||||
|
export interface CreateUserDTO {
|
||||||
|
username: string
|
||||||
|
email: string
|
||||||
|
password: string
|
||||||
|
firstName?: string
|
||||||
|
lastName?: string
|
||||||
|
roles?: UserRole[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Data for partial updates to a user */
|
||||||
|
export interface UpdateUserDTO {
|
||||||
|
username?: string
|
||||||
|
email?: string
|
||||||
|
password?: string
|
||||||
|
firstName?: string | null
|
||||||
|
lastName?: string | null
|
||||||
|
avatarUrl?: string | null
|
||||||
|
roles?: UserRole[]
|
||||||
|
isActive?: boolean
|
||||||
|
teamId?: string | null
|
||||||
|
metadata?: Record<string, unknown> | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Simple auth state slice for frontend state management */
|
||||||
|
export interface AuthState {
|
||||||
|
currentUser?: User | null
|
||||||
|
token?: string | null
|
||||||
|
isLoading: boolean
|
||||||
|
error?: string | null
|
||||||
|
}
|
||||||
421
frontend/src/pages/Administration.tsx
Normal file
421
frontend/src/pages/Administration.tsx
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
import React, { JSX, useEffect, useState } from "react";
|
||||||
|
import { fetchPlayers } from "./api";
|
||||||
|
import { User, UserRole } from "../components/interfaces/users";
|
||||||
|
|
||||||
|
// type User = {
|
||||||
|
// id: string;
|
||||||
|
// name: string;
|
||||||
|
// email: string;
|
||||||
|
// role: "user" | "admin";
|
||||||
|
// banned?: boolean;
|
||||||
|
// };
|
||||||
|
|
||||||
|
type Team = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
members: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type EventItem = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
date: string; // ISO
|
||||||
|
location?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const containerStyle: React.CSSProperties = {
|
||||||
|
display: "flex",
|
||||||
|
height: "100%",
|
||||||
|
gap: 20,
|
||||||
|
padding: 20,
|
||||||
|
boxSizing: "border-box",
|
||||||
|
fontFamily: "Inter, Roboto, Arial, sans-serif",
|
||||||
|
};
|
||||||
|
|
||||||
|
const sidebarStyle: React.CSSProperties = {
|
||||||
|
width: 220,
|
||||||
|
borderRight: "1px solid #e6e6e6",
|
||||||
|
paddingRight: 12,
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabButtonStyle = (active: boolean): React.CSSProperties => ({
|
||||||
|
display: "block",
|
||||||
|
width: "100%",
|
||||||
|
textAlign: "left",
|
||||||
|
padding: "8px 10px",
|
||||||
|
marginBottom: 6,
|
||||||
|
cursor: "pointer",
|
||||||
|
borderRadius: 6,
|
||||||
|
background: active ? "#0b5fff1a" : "transparent",
|
||||||
|
border: active ? "1px solid #0b5fff33" : "1px solid transparent",
|
||||||
|
});
|
||||||
|
|
||||||
|
const contentStyle: React.CSSProperties = {
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
overflowY: "auto",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Administration(): JSX.Element {
|
||||||
|
const [activeTab, setActiveTab] = useState<
|
||||||
|
"users" | "teams" | "events" | "settings"
|
||||||
|
>("users");
|
||||||
|
|
||||||
|
const [users, setUsers] = useState<User[]>([]);
|
||||||
|
const [teams, setTeams] = useState<Team[]>([]);
|
||||||
|
const [events, setEvents] = useState<EventItem[]>([]);
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Form state for creating event
|
||||||
|
const [newEventTitle, setNewEventTitle] = useState("");
|
||||||
|
const [newEventDate, setNewEventDate] = useState("");
|
||||||
|
const [newEventLocation, setNewEventLocation] = useState("");
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// fetch all admin resources when component mounts
|
||||||
|
if (token) refreshAll();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
async function refreshAll() {
|
||||||
|
if (!token) return;
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
// Replace these endpoints with your backend API
|
||||||
|
|
||||||
|
const users = await fetchPlayers(token);
|
||||||
|
setUsers(users);
|
||||||
|
console.log("Fetched users:", users);
|
||||||
|
const [uRes, tRes, eRes] = await Promise.all([
|
||||||
|
fetch("/api/admin/players"),
|
||||||
|
fetch("/api/admin/teams"),
|
||||||
|
fetch("/api/admin/events"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!uRes.ok || !tRes.ok || !eRes.ok) {
|
||||||
|
throw new Error("Failed to fetch admin resources");
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(uRes);
|
||||||
|
const [uJson, tJson, eJson] = await Promise.all([
|
||||||
|
uRes.json(),
|
||||||
|
tRes.json(),
|
||||||
|
eRes.json(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// setUsers(uRes as User[]);
|
||||||
|
setTeams(tJson as Team[]);
|
||||||
|
setEvents(eJson as EventItem[]);
|
||||||
|
|
||||||
|
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err?.message ?? "Unknown error");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleAdmin(user: User) {
|
||||||
|
const promote = user.Roles.includes(UserRole.Admin) ? false : true;
|
||||||
|
if (
|
||||||
|
!window.confirm(
|
||||||
|
`${promote ? "Promote" : "Demote"} ${user.Username} to ${
|
||||||
|
promote ? "admin" : "user"
|
||||||
|
}?`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/admin/users/${user.UUID}/role`, {
|
||||||
|
method: "PATCH",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ role: promote ? "admin" : "user" }),
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error("Failed to change role");
|
||||||
|
setUsers((prev) =>
|
||||||
|
prev.map((u) => (u.UUID === user.UUID ? { ...u, role: promote ? "admin" : "user" } : u))
|
||||||
|
);
|
||||||
|
} catch (err: any) {
|
||||||
|
alert(err?.message ?? "Failed to update role");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// async function toggleBan(user: User) {
|
||||||
|
// const ban = user.isActive;
|
||||||
|
// if (!window.confirm(`${ban ? "Ban" : "Unban"} ${user.username}?`)) return;
|
||||||
|
// try {
|
||||||
|
// const res = await fetch(`/api/admin/users/${user.id}/ban`, {
|
||||||
|
// method: "PATCH",
|
||||||
|
// headers: { "Content-Type": "application/json" },
|
||||||
|
// body: JSON.stringify({ banned: ban }),
|
||||||
|
// });
|
||||||
|
// if (!res.ok) throw new Error("Failed to update ban");
|
||||||
|
// setUsers((prev) => prev.map((u) => (u.id === user.id ? { ...u, banned: ban } : u)));
|
||||||
|
// } catch (err: any) {
|
||||||
|
// alert(err?.message ?? "Failed to update ban");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
async function deleteTeam(team: Team) {
|
||||||
|
if (!window.confirm(`Delete team "${team.name}"? This cannot be undone.`)) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/admin/teams/${team.id}`, { method: "DELETE" });
|
||||||
|
if (!res.ok) throw new Error("Failed to delete team");
|
||||||
|
setTeams((prev) => prev.filter((t) => t.id !== team.id));
|
||||||
|
} catch (err: any) {
|
||||||
|
alert(err?.message ?? "Failed to delete team");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createEvent(e: React.FormEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!newEventTitle || !newEventDate) {
|
||||||
|
alert("Title and date required");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const payload = {
|
||||||
|
title: newEventTitle,
|
||||||
|
date: new Date(newEventDate).toISOString(),
|
||||||
|
location: newEventLocation || undefined,
|
||||||
|
};
|
||||||
|
const res = await fetch("/api/admin/events", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
if (!res.ok) throw new Error("Failed to create event");
|
||||||
|
const created: EventItem = await res.json();
|
||||||
|
setEvents((prev) => [created, ...prev]);
|
||||||
|
setNewEventTitle("");
|
||||||
|
setNewEventDate("");
|
||||||
|
setNewEventLocation("");
|
||||||
|
setActiveTab("events");
|
||||||
|
} catch (err: any) {
|
||||||
|
alert(err?.message ?? "Failed to create event");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteEvent(ev: EventItem) {
|
||||||
|
if (!window.confirm(`Delete event "${ev.title}"?`)) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/admin/events/${ev.id}`, { method: "DELETE" });
|
||||||
|
if (!res.ok) throw new Error("Failed to delete event");
|
||||||
|
setEvents((prev) => prev.filter((x) => x.id !== ev.id));
|
||||||
|
} catch (err: any) {
|
||||||
|
alert(err?.message ?? "Failed to delete event");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={containerStyle}>
|
||||||
|
<aside style={sidebarStyle}>
|
||||||
|
<h3>Administration</h3>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
style={tabButtonStyle(activeTab === "users")}
|
||||||
|
onClick={() => setActiveTab("users")}
|
||||||
|
>
|
||||||
|
Users
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
style={tabButtonStyle(activeTab === "teams")}
|
||||||
|
onClick={() => setActiveTab("teams")}
|
||||||
|
>
|
||||||
|
Teams
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
style={tabButtonStyle(activeTab === "events")}
|
||||||
|
onClick={() => setActiveTab("events")}
|
||||||
|
>
|
||||||
|
Events
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
style={tabButtonStyle(activeTab === "settings")}
|
||||||
|
onClick={() => setActiveTab("settings")}
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginTop: 20 }}>
|
||||||
|
<button onClick={refreshAll} style={{ padding: "6px 10px", cursor: "pointer" }}>
|
||||||
|
Refresh
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{loading && <p style={{ marginTop: 12 }}>Loading...</p>}
|
||||||
|
{error && <p style={{ marginTop: 12, color: "red" }}>{error}</p>}
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<main style={contentStyle}>
|
||||||
|
{activeTab === "users" && (
|
||||||
|
<section>
|
||||||
|
<h2>Users</h2>
|
||||||
|
<p>Manage user accounts, roles and bans.</p>
|
||||||
|
<div style={{ marginTop: 12 }}>
|
||||||
|
{users.length === 0 ? (
|
||||||
|
<p>No users found.</p>
|
||||||
|
) : (
|
||||||
|
<table style={{ width: "100%", borderCollapse: "collapse" }}>
|
||||||
|
<thead>
|
||||||
|
<tr style={{ textAlign: "left", borderBottom: "1px solid #eee" }}>
|
||||||
|
<th style={{ padding: "8px 6px" }}>Name</th>
|
||||||
|
<th style={{ padding: "8px 6px" }}>Email</th>
|
||||||
|
<th style={{ padding: "8px 6px" }}>Role</th>
|
||||||
|
<th style={{ padding: "8px 6px" }}>Status</th>
|
||||||
|
<th style={{ padding: "8px 6px" }}>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{users.map((u) => (
|
||||||
|
<tr key={u.UUID} style={{ borderBottom: "1px solid #fafafa" }}>
|
||||||
|
<td style={{ padding: "8px 6px" }}>{u.Username}</td>
|
||||||
|
<td style={{ padding: "8px 6px" }}>{u.Email}</td>
|
||||||
|
<td style={{ padding: "8px 6px" }}>{u.Roles}</td>
|
||||||
|
{/* <td style={{ padding: "8px 6px" }}>{u.banned ? "Banned" : "Active"}</td> */}
|
||||||
|
{/* <td style={{ padding: "8px 6px" }}>
|
||||||
|
<button onClick={() => toggleAdmin(u)} style={{ marginRight: 8 }}>
|
||||||
|
{u.roles.includes(UserRole.Admin) ? "Demote" : "Promote"}
|
||||||
|
</button> */}
|
||||||
|
{/* <button onClick={() => toggleBan(u)}>{u.banned ? "Unban" : "Ban"}</button> */}
|
||||||
|
{/* </td> */}
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === "teams" && (
|
||||||
|
<section>
|
||||||
|
<h2>Teams</h2>
|
||||||
|
<p>Manage teams and membership.</p>
|
||||||
|
<div style={{ marginTop: 12 }}>
|
||||||
|
{teams.length === 0 ? (
|
||||||
|
<p>No teams found.</p>
|
||||||
|
) : (
|
||||||
|
<ul style={{ paddingLeft: 0, listStyle: "none" }}>
|
||||||
|
{teams.map((t) => (
|
||||||
|
<li
|
||||||
|
key={t.id}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
padding: "8px 0",
|
||||||
|
borderBottom: "1px solid #f2f2f2",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<strong>{t.name}</strong>
|
||||||
|
<div style={{ fontSize: 13, color: "#666" }}>{t.members} members</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button style={{ marginRight: 8 }}>Edit</button>
|
||||||
|
<button onClick={() => deleteTeam(t)}>Delete</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === "events" && (
|
||||||
|
<section>
|
||||||
|
<h2>Events</h2>
|
||||||
|
<p>Create and manage events</p>
|
||||||
|
|
||||||
|
<form onSubmit={createEvent} style={{ marginTop: 12, marginBottom: 24 }}>
|
||||||
|
<div style={{ display: "flex", gap: 8, alignItems: "center" }}>
|
||||||
|
<input
|
||||||
|
placeholder="Event title"
|
||||||
|
value={newEventTitle}
|
||||||
|
onChange={(e) => setNewEventTitle(e.target.value)}
|
||||||
|
style={{ padding: 8, flex: 1 }}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="date"
|
||||||
|
value={newEventDate}
|
||||||
|
onChange={(e) => setNewEventDate(e.target.value)}
|
||||||
|
style={{ padding: 8 }}
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
placeholder="Location (optional)"
|
||||||
|
value={newEventLocation}
|
||||||
|
onChange={(e) => setNewEventLocation(e.target.value)}
|
||||||
|
style={{ padding: 8 }}
|
||||||
|
/>
|
||||||
|
<button type="submit" style={{ padding: "8px 12px" }}>
|
||||||
|
Create
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{events.length === 0 ? (
|
||||||
|
<p>No events.</p>
|
||||||
|
) : (
|
||||||
|
<ul style={{ paddingLeft: 0, listStyle: "none" }}>
|
||||||
|
{events.map((ev) => (
|
||||||
|
<li
|
||||||
|
key={ev.id}
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
padding: "8px 0",
|
||||||
|
borderBottom: "1px solid #f2f2f2",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<strong>{ev.title}</strong>
|
||||||
|
<div style={{ fontSize: 13, color: "#666" }}>
|
||||||
|
{new Date(ev.date).toLocaleString()} {ev.location ? `• ${ev.location}` : ""}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button style={{ marginRight: 8 }}>Edit</button>
|
||||||
|
<button onClick={() => deleteEvent(ev)}>Delete</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{activeTab === "settings" && (
|
||||||
|
<section>
|
||||||
|
<h2>Settings</h2>
|
||||||
|
<p>Application-wide administrative settings.</p>
|
||||||
|
<div style={{ marginTop: 12 }}>
|
||||||
|
<p style={{ fontSize: 14, color: "#444" }}>
|
||||||
|
Use these settings to control global features. Implement actual controls as needed.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div style={{ marginTop: 12 }}>
|
||||||
|
<label style={{ display: "block", marginBottom: 8 }}>
|
||||||
|
<input type="checkbox" /> Allow public registration
|
||||||
|
</label>
|
||||||
|
<label style={{ display: "block", marginBottom: 8 }}>
|
||||||
|
<input type="checkbox" /> Require email verification
|
||||||
|
</label>
|
||||||
|
<div style={{ marginTop: 12 }}>
|
||||||
|
<button onClick={() => alert("Settings saved (stub)")}>Save settings</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -15,9 +15,10 @@ export default function Navigation() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{token ? (
|
{token ? (
|
||||||
|
<><Link to="/Administration" className="px-3 py-1 border rounded mr-2">Admin</Link>
|
||||||
<button onClick={logout} className="bg-red-500 px-3 py-1 rounded">
|
<button onClick={logout} className="bg-red-500 px-3 py-1 rounded">
|
||||||
Logout
|
Logout
|
||||||
</button>
|
</button></>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Link to="/register" className="px-3 py-1 border rounded">Register</Link>
|
<Link to="/register" className="px-3 py-1 border rounded">Register</Link>
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { fetchPlayers,createPlayer,deletePlayer,updatePlayer } from './api';
|
import { fetchPlayers,createPlayer,deletePlayer,updatePlayer } from './api';
|
||||||
import { Navigate, useNavigate } from 'react-router-dom';
|
import { Navigate, useNavigate } from 'react-router-dom';
|
||||||
|
import { User, UserRole } from '../components/interfaces/users';
|
||||||
|
|
||||||
|
// interface Player {
|
||||||
interface Player {
|
// id: string;
|
||||||
id: string;
|
// name: string;
|
||||||
name: string;
|
// email: string;
|
||||||
email: string;
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
export default function PlayerManagement() {
|
export default function PlayerManagement() {
|
||||||
const [players, setPlayers] = useState<Player[]>([]);
|
const [players, setPlayers] = useState<User[]>([]);
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
@@ -39,17 +39,19 @@ export default function PlayerManagement() {
|
|||||||
|
|
||||||
if (editingId !== null) {
|
if (editingId !== null) {
|
||||||
setPlayers(players.map(p =>
|
setPlayers(players.map(p =>
|
||||||
p.id === editingId ? { ...p, name, email } : p
|
p.UUID === editingId ? { ...p, name, email } : p
|
||||||
));
|
));
|
||||||
if (token) {
|
if (token) {
|
||||||
updatePlayer(editingId, { name, email }, token);
|
updatePlayer(editingId, { name, email }, token);
|
||||||
}
|
}
|
||||||
setEditingId(null);
|
setEditingId(null);
|
||||||
} else {
|
} else {
|
||||||
const newPlayer: Player = {
|
const newPlayer: User = {
|
||||||
id: "",
|
UUID: "",
|
||||||
name,
|
Username:"",
|
||||||
email,
|
Email: "",
|
||||||
|
Roles: [UserRole.Player],
|
||||||
|
IsActive: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
@@ -68,7 +70,7 @@ export default function PlayerManagement() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (id: string) => {
|
const handleDelete = (id: string) => {
|
||||||
setPlayers(players.filter(p => p.id !== id));
|
setPlayers(players.filter(p => p.UUID !== id));
|
||||||
if (token) {
|
if (token) {
|
||||||
deletePlayer(id, token);
|
deletePlayer(id, token);
|
||||||
}
|
}
|
||||||
@@ -124,19 +126,19 @@ export default function PlayerManagement() {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{players.map(player => (
|
{players.map(player => (
|
||||||
<tr key={player.id}>
|
<tr key={player.UUID}>
|
||||||
<td className="border px-4 py-2">{player.name}</td>
|
<td className="border px-4 py-2">{player.Username}</td>
|
||||||
<td className="border px-4 py-2">{player.email}</td>
|
<td className="border px-4 py-2">{player.Email}</td>
|
||||||
<td className="border px-4 py-2 space-x-2">
|
<td className="border px-4 py-2 space-x-2">
|
||||||
<button
|
<button
|
||||||
key={player.id}
|
key={player.UUID}
|
||||||
onClick={handleViewEdit(player.id)}
|
onClick={handleViewEdit(player.UUID)}
|
||||||
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
|
className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700"
|
||||||
>
|
>
|
||||||
Bearbeiten
|
Bearbeiten
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDelete(player.id)}
|
onClick={() => handleDelete(player.UUID)}
|
||||||
className="bg-red-500 text-white px-2 py-1 rounded"
|
className="bg-red-500 text-white px-2 py-1 rounded"
|
||||||
>
|
>
|
||||||
Löschen
|
Löschen
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import { fetchTournament, updateTournament, registerTeam } from './api';
|
import { fetchTournament, updateTournament, registerTeam } from './api';
|
||||||
import { useAuth } from './AuthContext';
|
import { useAuth } from './AuthContext';
|
||||||
|
|
||||||
import { Tournament } from './types';
|
import { Tournament } from '../components/interfaces/types';
|
||||||
|
|
||||||
export default function TournamentDetails() {
|
export default function TournamentDetails() {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
import { use, useEffect, useState } from 'react';
|
import { use, useEffect, useState } from 'react';
|
||||||
import { fetchTournaments, fetchTournament } from './api'; // Importiere die API-Funktion
|
import { fetchTournaments, fetchTournament } from './api'; // Importiere die API-Funktion
|
||||||
import type { Tournament } from './types';
|
import type { Tournament } from '../components/interfaces/types';
|
||||||
|
|
||||||
function useQuery() {
|
function useQuery() {
|
||||||
return new URLSearchParams(useLocation().search);
|
return new URLSearchParams(useLocation().search);
|
||||||
|
|||||||
@@ -2,13 +2,8 @@ import React, { useEffect, useState } from 'react';
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { getUserFromToken } from '../components/utils/jwt'; // Importiere die Funktion zum Decodieren des Tokens
|
import { getUserFromToken } from '../components/utils/jwt'; // Importiere die Funktion zum Decodieren des Tokens
|
||||||
import {fetchPlayer} from './api'; // Importiere die Funktion zum Abrufen des Spielers
|
import {fetchPlayer} from './api'; // Importiere die Funktion zum Abrufen des Spielers
|
||||||
|
import { User } from '../components/interfaces/users';
|
||||||
|
|
||||||
interface Player {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
email: string;
|
|
||||||
role: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ViewEditPlayer = () => {
|
const ViewEditPlayer = () => {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
@@ -16,7 +11,7 @@ const ViewEditPlayer = () => {
|
|||||||
const currentUser = token ? getUserFromToken(token) : null;
|
const currentUser = token ? getUserFromToken(token) : null;
|
||||||
const isAdmin = currentUser?.role === 'admin';
|
const isAdmin = currentUser?.role === 'admin';
|
||||||
|
|
||||||
const [player, setPlayer] = useState<Player | null>(null);
|
const [player, setPlayer] = useState<User | null>(null);
|
||||||
const [name, setName] = useState('');
|
const [name, setName] = useState('');
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [role, setRole] = useState('');
|
const [role, setRole] = useState('');
|
||||||
@@ -32,9 +27,9 @@ const ViewEditPlayer = () => {
|
|||||||
const data = await fetchPlayer(token, id);
|
const data = await fetchPlayer(token, id);
|
||||||
console.log("Geladener Spieler:", data);
|
console.log("Geladener Spieler:", data);
|
||||||
setPlayer(data);
|
setPlayer(data);
|
||||||
setName(data.name);
|
setName(data.Username);
|
||||||
setEmail(data.email);
|
setEmail(data.Email);
|
||||||
setRole(data.role);
|
setRole(data.Roles);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setMessage('Spieler konnte nicht geladen werden.');
|
setMessage('Spieler konnte nicht geladen werden.');
|
||||||
}
|
}
|
||||||
@@ -77,7 +72,7 @@ const ViewEditPlayer = () => {
|
|||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => setName(e.target.value)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<p>{player.name}</p>
|
<p>{player.Username}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -91,7 +86,7 @@ const ViewEditPlayer = () => {
|
|||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<p>{player.email}</p>
|
<p>{player.Email}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -107,7 +102,7 @@ const ViewEditPlayer = () => {
|
|||||||
<option value="admin">Admin</option>
|
<option value="admin">Admin</option>
|
||||||
</select>
|
</select>
|
||||||
) : (
|
) : (
|
||||||
<p>{player.role}</p>
|
<p>{player.Roles}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export async function fetchPlayers(token: string) {
|
|||||||
return res.json();
|
return res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createPlayer(player: { name: string, email: string }, token: string) {
|
export async function createPlayer(player: { Username: string, Email: string }, token: string) {
|
||||||
const res = await fetch(`${API_URL}/players`, {
|
const res = await fetch(`${API_URL}/players`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
|
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
|
||||||
|
|||||||
Reference in New Issue
Block a user