diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 4b543e0..eee46b5 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -1,6 +1,7 @@ package main import ( + "log" "os" "volleyball/internal/auth" "volleyball/internal/database" @@ -63,12 +64,16 @@ func main() { player.CreatePlayer(c, db.GetDB()) }) api.PUT("/players/:id", func(c *gin.Context) { + log.Println("PUT /players/:id called", c.Params) player.UpdatePlayer(c, db.GetDB()) }) api.DELETE("/players/:id", func(c *gin.Context) { player.DeletePlayer(c, db.GetDB()) // c.JSON(http.StatusOK, gin.H{"message": "Player deleted successfully"}) }) + api.GET("/teams", func(c *gin.Context) { + + }) port := os.Getenv("PORT") if port == "" { diff --git a/backend/internal/player/handler.go b/backend/internal/player/handler.go index df543aa..ad48393 100644 --- a/backend/internal/player/handler.go +++ b/backend/internal/player/handler.go @@ -100,13 +100,15 @@ func GetPlayer(c *gin.Context, db *sql.DB, id string) { } func UpdatePlayer(c *gin.Context, db *sql.DB) { + log.Println(c) playerID := c.Param("id") if playerID == "" { common.RespondError(c, http.StatusBadRequest, "Player ID is required") return } - - if playerID != c.GetString("userId") || c.GetString("role") != "admin" { + log.Println("role: ", c.GetString("role")) + // playerID != c.GetString("userId") || + if c.GetString("role") != "admin" { common.RespondError(c, http.StatusForbidden, "You do not have permission to update this player") return } diff --git a/backend/internal/player/model.go b/backend/internal/player/model.go index 05aed93..399d984 100644 --- a/backend/internal/player/model.go +++ b/backend/internal/player/model.go @@ -11,20 +11,20 @@ import ( ) type User struct { - UUID string `db:"uuid" sql:"VARCHAR(255)" index:"true"` - Username string `db:"username" sql:"VARCHAR(100)"` - Email string `db:"email" sql:"VARCHAR(255)" index:"true"` - lastname sql.NullString `db:"lastname" sql:"VARCHAR(100)"` - firstname sql.NullString `db:"firstname" sql:"VARCHAR(100)"` - password string `db:"password_hash" sql:"VARCHAR(255)"` - phone sql.NullString `db:"phone" sql:"VARCHAR(20)"` - avatarURL sql.NullString `db:"avatar_url" sql:"VARCHAR(255)"` - IsActive sql.NullBool `db:"is_active" sql:"BOOLEAN"` - birthday *time.Time `db:"birthday" sql:"DATE"` - createdAt *time.Time `db:"created_at" sql:"TIMESTAMP"` - updatedAt *time.Time `db:"updated_at" sql:"TIMESTAMP"` - LastLogin *time.Time `db:"last_login" sql:"TIMESTAMP"` - Role []string `ignore:"true"` // wird NICHT in der DB angelegt + UUID string `db:"uuid" sql:"VARCHAR(255)" index:"true"` + Username string `db:"username" sql:"VARCHAR(100)"` + Email string `db:"email" sql:"VARCHAR(255)" index:"true"` + lastname sql.NullString `db:"lastname" sql:"VARCHAR(100)"` + firstname sql.NullString `db:"firstname" sql:"VARCHAR(100)"` + password string `db:"password_hash" sql:"VARCHAR(255)"` + phone sql.NullString `db:"phone" sql:"VARCHAR(20)"` + avatarURL sql.NullString `db:"avatar_url" sql:"VARCHAR(255)"` + IsActive sql.NullBool `db:"is_active" sql:"BOOLEAN"` + birthday *time.Time `db:"birthday" sql:"DATE"` + Created_at *time.Time `db:"created_at" sql:"TIMESTAMP"` + updatedAt *time.Time `db:"updated_at" sql:"TIMESTAMP"` + LastLogin *time.Time `db:"last_login" sql:"TIMESTAMP"` + Role []string `ignore:"true"` // wird NICHT in der DB angelegt } @@ -236,7 +236,7 @@ func GetAllPlayers(db *sql.DB) ([]User, error) { &player.lastname, &player.birthday, &player.IsActive, - &player.createdAt, + &player.Created_at, (*pq.StringArray)(&player.Role), ); err != nil { log.Printf("Error scanning player row: %v", err) @@ -305,7 +305,7 @@ func updatePlayer(db *sql.DB, player User) error { log.Printf("Updating player: ID=%v, Name=%v, Email=%v", player.UUID, player.Username, player.Email) - stmt := "UPDATE public.users SET name = $1, email = $2 WHERE id = $3" + stmt := "UPDATE public.users SET username = $1, email = $2 WHERE uuid = $3" _, err := db.Exec(stmt, player.Username, player.Email, player.UUID) if err != nil { log.Printf("Error updating player in database: %v", err) diff --git a/backend/internal/team/handler.go b/backend/internal/team/handler.go new file mode 100644 index 0000000..03e11bc --- /dev/null +++ b/backend/internal/team/handler.go @@ -0,0 +1,31 @@ +package team + +import ( + "database/sql" + "log" + "net/http" + "volleyball/internal/common" + + "github.com/gin-gonic/gin" +) + +func GetTeams(c *gin.Context, db *sql.DB) { + log.Println(c.GetString("userId"), c.GetString("email"), c.GetString("role")) + // Simulate fetching players from a database + + teams, err := GetAllTeams(db) + if err != nil { + log.Printf("Error retrieving teams: %v", err) + common.RespondError(c, http.StatusInternalServerError, "Failed to retrieve players") + return + } + + if len(teams) > 0 { + log.Printf("User %s (%s) requested players", c.GetString("userId"), c.GetString("email")) + c.JSON(http.StatusOK, teams) + return + } + log.Printf("User %s (%s) requested players, but none found", c.GetString("userId"), c.GetString("email")) + + common.RespondError(c, http.StatusNotFound, "No Players found") +} diff --git a/backend/internal/team/model.go b/backend/internal/team/model.go new file mode 100644 index 0000000..35e7240 --- /dev/null +++ b/backend/internal/team/model.go @@ -0,0 +1,25 @@ +package team + +import ( + "database/sql" +) + +type Player struct { + UUID string `db:"uuid" sql:"VARCHAR(255)" index:"true"` + Email string `db:"email" sql:"VARCHAR(255)" index:"true"` + Username string `db:"username" sql:"VARCHAR(100)"` + FirstName string `db:"firstname" sql:"VARCHAR(100)"` + LastName string `db:"lastname" sql:"VARCHAR(100)"` +} + +type Team struct { + UUID string `db:"uuid" sql:"VARCHAR(255)" index:"true"` + Name string `db:"username" sql:"VARCHAR(100)"` + Email string `db:"email" sql:"VARCHAR(255)" index:"true"` + PlayersUUID []Player `db:"players" sql:"JSVARCHAR(255)"` +} + +func GetAllTeams(db *sql.DB) ([]Team, error) { + // Implementation to retrieve all teams from the database + return []Team{}, nil +} diff --git a/frontend/src/components/interfaces/Team.tsx b/frontend/src/components/interfaces/Team.tsx new file mode 100644 index 0000000..98f18ca --- /dev/null +++ b/frontend/src/components/interfaces/Team.tsx @@ -0,0 +1,8 @@ +import {UserBaseInformation } from "./users"; + +export interface Team { + teamUUID: string; + name: string; + Player: UserBaseInformation[]; + created_at?: string; // ISO date +} \ No newline at end of file diff --git a/frontend/src/components/interfaces/users.tsx b/frontend/src/components/interfaces/users.tsx index 879bb41..b7cb52e 100644 --- a/frontend/src/components/interfaces/users.tsx +++ b/frontend/src/components/interfaces/users.tsx @@ -11,50 +11,54 @@ export enum UserRole { /** Primary user model returned from the API */ -export interface User { +export interface UserBaseInformation { UUID: string Username: string + AvatarUrl?: string + IsActive: boolean + +} + + +export interface User extends UserBaseInformation { Email: string FirstName?: string LastName?: string - AvatarUrl?: string Role: UserRole[] - IsActive: boolean Phone?: string - TeamId?: string[] - CreatedAt?: string // ISO date - UpdatedAt?: string // ISO date + Created_at?: string // ISO date + Updated_at?: 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[] -} +// 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 | null -} +// 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 | null +// } /** Simple auth state slice for frontend state management */ -export interface AuthState { - currentUser?: User | null - token?: string | null - isLoading: boolean - error?: string | null -} \ No newline at end of file +// export interface AuthState { +// currentUser?: User | null +// token?: string | null +// isLoading: boolean +// error?: string | null +// } \ No newline at end of file diff --git a/frontend/src/components/requests/TeamData.tsx b/frontend/src/components/requests/TeamData.tsx new file mode 100644 index 0000000..cbf1102 --- /dev/null +++ b/frontend/src/components/requests/TeamData.tsx @@ -0,0 +1,10 @@ +const API_URL = 'http://localhost:8080/api'; + +export async function getTeams(token:string) +{ + const res = await fetch(`${API_URL}/team/`, { + headers: { Authorization: `Bearer ${token}` }, + }); + if (!res.ok) throw new Error('Fehler beim Laden des Spielers'); + return res.json(); +} diff --git a/frontend/src/pages/Administration/Administration.tsx b/frontend/src/pages/Administration/Administration.tsx index f33b4f0..e6a1b4d 100644 --- a/frontend/src/pages/Administration/Administration.tsx +++ b/frontend/src/pages/Administration/Administration.tsx @@ -3,6 +3,8 @@ import { fetchPlayers } from "../api"; import { User, UserRole } from "../../components/interfaces/users"; import UsersPage from "./Users"; import TMP from "./TMP"; +import TeamManagement from "../Teams"; +import Teams from "./Teams"; // type User = { // id: string; @@ -227,36 +229,8 @@ export default function Administration(): JSX.Element { {activeTab === "teams" && (
-

Teams

-

Manage teams and membership.

-
- {teams.length === 0 ? ( -

No teams found.

- ) : ( -
    - {teams.map((t) => ( -
  • -
    - {t.name} -
    {t.members} members
    -
    -
    - - -
    -
  • - ))} -
- )} -
+ +
)} diff --git a/frontend/src/pages/Administration/Teams.tsx b/frontend/src/pages/Administration/Teams.tsx new file mode 100644 index 0000000..69312b8 --- /dev/null +++ b/frontend/src/pages/Administration/Teams.tsx @@ -0,0 +1,51 @@ +import React, { useEffect, useState } from "react"; +import { Team } from "../../components/interfaces/Team"; +import { get } from "http"; +import { getTeams } from "../../components/requests/TeamData"; + + + +const Teams: React.FC = () => { + const [teams, setTeams] = useState([]); + + useEffect(() => { + // Replace with API call in production + load(); + }, []); + + async function load() + { + try { + const teams : Team[] = await getTeams(localStorage.getItem("token") || ""); + setTeams(teams); + } catch (error) { + console.error("Fehler beim Laden der Teams:", error); + } + finally { + } + } + + return ( +
+

Teams Administration

+ + + + + + + + + {teams.map(team => ( + + + + + ))} + +
Team Name
{team.name}
+
+ ); +}; + +export default Teams; \ No newline at end of file diff --git a/frontend/src/pages/Administration/Users.tsx b/frontend/src/pages/Administration/Users.tsx index 60ab496..b93dae1 100644 --- a/frontend/src/pages/Administration/Users.tsx +++ b/frontend/src/pages/Administration/Users.tsx @@ -51,19 +51,25 @@ export default function UsersPage(): JSX.Element { params.set("page", String(page)); params.set("limit", String(pageSize)); - const token = localStorage.getItem('token'); - if (!token) throw new Error("No auth token found"); - const data = await fetchPlayers(token); - console.log("Loaded users:", data); - // const res = await fetch(`/api/users?${params.toString()}`, { - // signal: ac.signal, - // }); + console.log("Fetching users with params:", params.toString()); + + const token = localStorage.getItem('token'); + if (!token) throw new Error("No auth token found"); + const data = await fetchPlayers(token); + + const filteredData = data.filter((user: User) => + user.Username.toLowerCase().includes(query.toLowerCase()) || + user.Email.toLowerCase().includes(query.toLowerCase()) + ); + setUsers(filteredData); + console.log("Loaded users:", data); + // if (!res.ok) throw new Error(`Failed to load users: ${res.status}`); // const data = await res.json(); // Expecting { users: User[] } or User[] — handle both. - const list: User[] = Array.isArray(data) ? data : data.users ?? []; - setUsers(list); + // const list: User[] = Array.isArray(data) ? data : data.users ?? []; + // setUsers(list); } catch (err: any) { if (err.name !== "AbortError") setError(err.message || String(err)); } finally { @@ -99,31 +105,21 @@ export default function UsersPage(): JSX.Element { const payload = { ...form }; let res: Response; if (editing) { + + console.log("Editing user:", editing.UUID, "with data:", form); res = await updatePlayer(editing.UUID, { Username: form.name, Email: form.email, + Role: form.role.split(","), }, localStorage.getItem('token') || ""); - // res = await createPlayer({ - // Username: form.name, - // Email: form.email, - // }, localStorage.getItem('token') || ""); - - // res = await fetch(`/api/users/${editing.UUID}`, { - // method: "PUT", - // headers: { "Content-Type": "application/json" }, - // body: JSON.stringify(payload), - // }); + } else { res = await createPlayer({ Username: form.name, Email: form.email, }, localStorage.getItem('token') || ""); console.log("Create response:", res); - // res = await fetch(`/api/users`, { - // method: "POST", - // headers: { "Content-Type": "application/json" }, - // body: JSON.stringify(payload), - // }); + } // Parse response body (support both fetch Response and already-parsed results) @@ -176,8 +172,8 @@ export default function UsersPage(): JSX.Element { placeholder="Search by name or email..." value={query} onChange={(e) => { + console.log("Search query:", e.target.value); setQuery(e.target.value); - setPage(1); }} style={{ padding: 6, flex: 1 }} /> @@ -229,7 +225,7 @@ export default function UsersPage(): JSX.Element { {u.IsActive ? "Yes" : "No"} - {u.CreatedAt ? new Date(u.CreatedAt).toLocaleString() : "—"} + {u.Created_at ? new Date(u.Created_at).toLocaleString() : "—"}