From 13784196ed7ad5df77b08e233b5fc05d858ae3c9 Mon Sep 17 00:00:00 2001 From: IgorPolyakov Date: Fri, 19 Apr 2024 13:38:44 +0300 Subject: [PATCH] add user handler, user view fix small mistake --- api/swagger.yaml | 1 - db/migration/init.sql | 7 +-- src/go.mod | 2 - src/go.sum | 4 -- src/lib/api/games.go | 4 +- src/lib/api/users.go | 95 ++++++++++++++++++++++++++++++++++---- src/lib/models/user.go | 11 +++-- src/lib/repository/user.go | 18 ++++---- src/lib/view/user.go | 22 ++++++++- 9 files changed, 127 insertions(+), 37 deletions(-) diff --git a/api/swagger.yaml b/api/swagger.yaml index d302c8e..dca0ee4 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -130,7 +130,6 @@ paths: content: application/json: schema: - # fixme тут нужно убрать id из запроса $ref: '#/components/schemas/GameRequest' required: true responses: diff --git a/db/migration/init.sql b/db/migration/init.sql index 497ea7d..8887ee8 100644 --- a/db/migration/init.sql +++ b/db/migration/init.sql @@ -1,16 +1,17 @@ -- Пользователи CREATE TABLE users ( id SERIAL PRIMARY KEY, - user_name VARCHAR(255) NOT NULL, + user_name VARCHAR(255) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, avatar_url VARCHAR(255), - role VARCHAR(255) NOT NULL CHECK (role IN ('admin', 'player')) + status VARCHAR(255), + role VARCHAR(255) NOT NULL CHECK (role IN ('admin', 'player', 'guest')) ); -- Команды CREATE TABLE teams ( id SERIAL PRIMARY KEY, - team_name VARCHAR(255) NOT NULL, + team_name VARCHAR(255) UNIQUE NOT NULL, description VARCHAR(255) NOT NULL, university VARCHAR(255), social_links TEXT, diff --git a/src/go.mod b/src/go.mod index 352f2eb..1c61f50 100644 --- a/src/go.mod +++ b/src/go.mod @@ -3,14 +3,12 @@ module ctf01d go 1.22.2 require ( - github.com/go-sql-driver/mysql v1.8.1 github.com/gorilla/mux v1.8.1 github.com/ilyakaznacheev/cleanenv v1.5.0 github.com/lib/pq v1.2.0 ) require ( - filippo.io/edwards25519 v1.1.0 // indirect github.com/BurntSushi/toml v1.2.1 // indirect github.com/joho/godotenv v1.5.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/src/go.sum b/src/go.sum index 411593a..6fcd19e 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,9 +1,5 @@ -filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= -filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= -github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4= diff --git a/src/lib/api/games.go b/src/lib/api/games.go index c267031..bbfed4d 100644 --- a/src/lib/api/games.go +++ b/src/lib/api/games.go @@ -10,7 +10,6 @@ import ( "net/http" "time" - _ "github.com/go-sql-driver/mysql" "github.com/gorilla/mux" ) @@ -37,7 +36,7 @@ func CreateGameHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) { Description: game.Description, } if err := gameRepo.Create(r.Context(), newGame); err != nil { - respondWithJSON(w, http.StatusInternalServerError, map[string]string{"error": "Failed to create game"}) + respondWithJSON(w, http.StatusInternalServerError, map[string]string{"error": "Failed to create game: " + err.Error()}) return } respondWithJSON(w, http.StatusOK, map[string]string{"data": "Game created successfully"}) @@ -100,6 +99,7 @@ func UpdateGameHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) { respondWithJSON(w, http.StatusOK, map[string]string{"data": "Game updated successfully"}) } +// fixme это общий респондер - надо его вынести отсюда func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { w.Header().Set("Content-Type", "application/json") response, err := json.Marshal(payload) diff --git a/src/lib/api/users.go b/src/lib/api/users.go index b6ece13..b3333fc 100644 --- a/src/lib/api/users.go +++ b/src/lib/api/users.go @@ -1,31 +1,106 @@ package api import ( + "crypto/sha1" + "ctf01d/lib/models" + "ctf01d/lib/repository" + "ctf01d/lib/view" "database/sql" + "encoding/hex" + "encoding/json" "net/http" + + "github.com/gorilla/mux" ) +type RequestUser struct { + Username string `json:"user_name"` + Role string `json:"role"` + AvatarUrl string `json:"avatar_url"` + Status string `json:"status"` + Password string `json:"password"` +} + func CreateUserHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) + var user RequestUser + if err := json.NewDecoder(r.Body).Decode(&user); err != nil { + respondWithJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid request payload"}) + return + } + userRepo := repository.NewUserRepository(db) + newUser := &models.User{ + Username: user.Username, + Role: user.Role, + Status: user.Status, + PasswordHash: HashPassword(user.Password), + } + if err := userRepo.Create(r.Context(), newUser); err != nil { + respondWithJSON(w, http.StatusInternalServerError, map[string]string{"error": "Failed to create user: " + err.Error()}) + return + } + respondWithJSON(w, http.StatusOK, map[string]string{"data": "User created successfully"}) +} + +// ей тут не место, вынести в - tool, добавить соль в конфиг и солить пароли +func HashPassword(s string) string { + h := sha1.New() + h.Write([]byte(s)) + return hex.EncodeToString(h.Sum(nil)) } func DeleteUserHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) + vars := mux.Vars(r) + id := vars["id"] + userRepo := repository.NewUserRepository(db) + if err := userRepo.Delete(r.Context(), id); err != nil { + respondWithJSON(w, http.StatusInternalServerError, map[string]string{"error": "Failed to delete user"}) + return + } + respondWithJSON(w, http.StatusOK, map[string]string{"data": "User deleted successfully"}) } func GetUserByIdHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) + vars := mux.Vars(r) + id := vars["id"] + userRepo := repository.NewUserRepository(db) + user, err := userRepo.GetById(r.Context(), id) + if err != nil { + respondWithJSON(w, http.StatusInternalServerError, map[string]string{"error": "Failed to fetch user"}) + return + } + respondWithJSON(w, http.StatusOK, view.NewUserFromModel(user)) } func ListUsersHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) + userRepo := repository.NewUserRepository(db) + users, err := userRepo.List(r.Context()) + if err != nil { + respondWithJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()}) + return + } + respondWithJSON(w, http.StatusOK, view.NewUsersFromModels(users)) } func UpdateUserHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=UTF-8") - w.WriteHeader(http.StatusOK) + // fixme update не проверяет есть ли запись в бд + var user RequestUser + if err := json.NewDecoder(r.Body).Decode(&user); err != nil { + respondWithJSON(w, http.StatusBadRequest, map[string]string{"error": "Invalid request payload"}) + return + } + userRepo := repository.NewUserRepository(db) + updateUser := &models.User{ + Username: user.Username, + Role: user.Role, + Status: user.Status, + } + vars := mux.Vars(r) + id := vars["id"] + updateUser.Id = id + err := userRepo.Update(r.Context(), updateUser) + if err != nil { + respondWithJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + respondWithJSON(w, http.StatusOK, map[string]string{"data": "User updated successfully"}) } diff --git a/src/lib/models/user.go b/src/lib/models/user.go index 64660d7..b16ab24 100644 --- a/src/lib/models/user.go +++ b/src/lib/models/user.go @@ -1,9 +1,10 @@ package models type User struct { - Id string `db:"id"` - Username string `db:"username"` - Role string `db:"role"` - AvatarUrl string `db:"avatar_url"` - Status string `db:"status"` + Id string `db:"id"` + Username string `db:"user_name"` + Role string `db:"role"` + AvatarUrl string `db:"avatar_url"` + Status string `db:"status"` + PasswordHash string `db:"password_hash"` } diff --git a/src/lib/repository/user.go b/src/lib/repository/user.go index a3d64d6..eaa2e5d 100644 --- a/src/lib/repository/user.go +++ b/src/lib/repository/user.go @@ -11,7 +11,7 @@ type UserRepository interface { GetById(ctx context.Context, id string) (*models.User, error) Update(ctx context.Context, user *models.User) error Delete(ctx context.Context, id string) error - List(ctx context.Context) ([]models.User, error) + List(ctx context.Context) ([]*models.User, error) } type userRepo struct { @@ -23,13 +23,13 @@ func NewUserRepository(db *sql.DB) UserRepository { } func (r *userRepo) Create(ctx context.Context, user *models.User) error { - query := `INSERT INTO users (username, avatar_url, role) VALUES ($1, $2, $3)` - _, err := r.db.ExecContext(ctx, query, user.Username, user.AvatarUrl, user.Role) + query := `INSERT INTO users (user_name, avatar_url, role, status, password_hash) VALUES ($1, $2, $3, $4, $5)` + _, err := r.db.ExecContext(ctx, query, user.Username, user.AvatarUrl, user.Role, user.Status, user.PasswordHash) return err } func (r *userRepo) GetById(ctx context.Context, id string) (*models.User, error) { - query := `SELECT id, username, avatar_url, role FROM users WHERE id = $1` + query := `SELECT id, user_name, avatar_url, role FROM users WHERE id = $1` user := &models.User{} err := r.db.QueryRowContext(ctx, query, id).Scan(&user.Id, &user.Username, &user.AvatarUrl, &user.Role) if err != nil { @@ -39,7 +39,7 @@ func (r *userRepo) GetById(ctx context.Context, id string) (*models.User, error) } func (r *userRepo) Update(ctx context.Context, user *models.User) error { - query := `UPDATE users SET username = $1, avatar_url = $2, role = $3 WHERE id = $4` + query := `UPDATE users SET user_name = $1, avatar_url = $2, role = $3 WHERE id = $4` _, err := r.db.ExecContext(ctx, query, user.Username, user.AvatarUrl, user.Role, user.Id) return err } @@ -50,21 +50,21 @@ func (r *userRepo) Delete(ctx context.Context, id string) error { return err } -func (r *userRepo) List(ctx context.Context) ([]models.User, error) { - query := `SELECT id, username, avatar_url, role FROM users` +func (r *userRepo) List(ctx context.Context) ([]*models.User, error) { + query := `SELECT id, user_name, avatar_url, role FROM users` rows, err := r.db.QueryContext(ctx, query) if err != nil { return nil, err } defer rows.Close() - var users []models.User + var users []*models.User for rows.Next() { var user models.User if err := rows.Scan(&user.Id, &user.Username, &user.AvatarUrl, &user.Role); err != nil { return nil, err } - users = append(users, user) + users = append(users, &user) } return users, nil } diff --git a/src/lib/view/user.go b/src/lib/view/user.go index f459dfc..b7c87bb 100644 --- a/src/lib/view/user.go +++ b/src/lib/view/user.go @@ -1,9 +1,29 @@ package view +import "ctf01d/lib/models" + type User struct { Id string `json:"id,omitempty"` - Username string `json:"username,omitempty"` + Username string `json:"user_name,omitempty"` Role string `json:"role,omitempty"` AvatarUrl string `json:"avatar_url,omitempty"` Status string `json:"status,omitempty"` } + +func NewUserFromModel(u *models.User) *User { + return &User{ + Id: u.Id, + Username: u.Username, + Role: u.Role, + AvatarUrl: u.AvatarUrl, + Status: u.Status, + } +} + +func NewUsersFromModels(ms []*models.User) []*User { + var users []*User + for _, m := range ms { + users = append(users, NewUserFromModel(m)) + } + return users +}