diff --git a/pkg/api/api.go b/pkg/api/api.go index 0774fd8..ba7c2f9 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -70,6 +70,9 @@ func NewAPI(config Config) (*API, error) { apiMux.HandleFunc("/get_file_info", api.HandleGetFileInfo) apiMux.HandleFunc("/get_file_stream_direct", api.HandleGetFileStreamDirect) apiMux.HandleFunc("/prepare_file_stream_direct", api.HandlePrepareFileStreamDirect) + // user + apiMux.HandleFunc("/login", api.HandleLogin) + apiMux.HandleFunc("/register", api.HandleRegister) // below needs token apiMux.HandleFunc("/walk", api.HandleWalk) apiMux.HandleFunc("/reset", api.HandleReset) diff --git a/pkg/api/handle_error.go b/pkg/api/handle_error.go index 3f80bab..818eca9 100644 --- a/pkg/api/handle_error.go +++ b/pkg/api/handle_error.go @@ -6,6 +6,10 @@ import ( "net/http" ) +type Error struct { + Error string `json:"error,omitempty"` +} + func (api *API) HandleError(w http.ResponseWriter, r *http.Request, err error) { api.HandleErrorString(w, r, err.Error()) } @@ -20,8 +24,8 @@ func (api *API) HandleErrorString(w http.ResponseWriter, r *http.Request, errorS func (api *API) HandleErrorStringCode(w http.ResponseWriter, r *http.Request, errorString string, code int) { log.Println("[api] [Error]", code, errorString) - errStatus := &Status{ - Status: errorString, + errStatus := &Error{ + Error: errorString, } w.WriteHeader(code) json.NewEncoder(w).Encode(errStatus) diff --git a/pkg/api/handle_user.go b/pkg/api/handle_user.go new file mode 100644 index 0000000..92d19be --- /dev/null +++ b/pkg/api/handle_user.go @@ -0,0 +1,83 @@ +package api + +import ( + "encoding/json" + "log" + "msw-open-music/pkg/database" + "net/http" +) + +type LoginRequest struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type LoginResponse struct { + User *database.User `json:"user"` +} + +func (api *API) HandleLogin(w http.ResponseWriter, r *http.Request) { + // Get method will login as anonymous user + if r.Method == "GET" { + log.Println("Login as anonymous user") + user, err := api.Db.LoginAsAnonymous() + if err != nil { + api.HandleError(w, r, err) + return + } + resp := &LoginResponse{ + User: user, + } + err = json.NewEncoder(w).Encode(resp) + return + } + + var request LoginRequest + err := json.NewDecoder(r.Body).Decode(&request) + if err != nil { + api.HandleError(w, r, err) + return + } + + log.Println("Login as user", request.Username) + + user, err := api.Db.Login(request.Username, request.Password) + if err != nil { + api.HandleError(w, r, err) + return + } + + resp := &LoginResponse{ + User: user, + } + err = json.NewEncoder(w).Encode(resp) + if err != nil { + api.HandleError(w, r, err) + return + } +} + +type RegisterRequest struct { + Username string `json:"username"` + Password string `json:"password"` + Role int64 `json:"role"` +} + +func (api *API) HandleRegister(w http.ResponseWriter, r *http.Request) { + var request RegisterRequest + err := json.NewDecoder(r.Body).Decode(&request) + if err != nil { + api.HandleError(w, r, err) + return + } + + log.Println("Register user", request.Username) + + err = api.Db.Register(request.Username, request.Password, request.Role) + if err != nil { + api.HandleError(w, r, err) + return + } + + api.HandleOK(w, r) +} diff --git a/pkg/database/method_user.go b/pkg/database/method_user.go new file mode 100644 index 0000000..ecd3f5c --- /dev/null +++ b/pkg/database/method_user.go @@ -0,0 +1,31 @@ +package database + +func (database *Database) Login(username string, password string) (*User, error) { + user := &User{} + + // get user from database + err := database.stmt.getUser.QueryRow(username, password).Scan(&user.ID, &user.Username, &user.Role, &user.AvatarId) + if err != nil { + return user, err + } + return user, nil +} + +func (database *Database) LoginAsAnonymous() (*User, error) { + user := &User{} + + // get user from database + err := database.stmt.getAnonymousUser.QueryRow().Scan(&user.ID, &user.Username, &user.Role, &user.AvatarId) + if err != nil { + return user, err + } + return user, nil +} + +func (database *Database) Register(username string, password string, usertype int64) (error) { + _, err := database.stmt.insertUser.Exec(username, password, usertype, 0) + if err != nil { + return err + } + return nil +} diff --git a/pkg/database/sql_stmt.go b/pkg/database/sql_stmt.go index 2a5e354..e4bad08 100644 --- a/pkg/database/sql_stmt.go +++ b/pkg/database/sql_stmt.go @@ -25,10 +25,13 @@ var initFeedbacksTableQuery = `CREATE TABLE IF NOT EXISTS feedbacks ( header TEXT NOT NULL );` +// User table schema definition +// role: 0 - Anonymous User, 1 - Admin, 2 - User var initUsersTableQuery = `CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY, - username TEXT NOT NULL, + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT NOT NULL UNIQUE, password TEXT NOT NULL, + role INTEGER NOT NULL, avatar_id INTEGER NOT NULL, FOREIGN KEY(avatar_id) REFERENCES avatars(id) );` @@ -153,6 +156,17 @@ LIMIT ?;` var insertFeedbackQuery = `INSERT INTO feedbacks (time, feedback, header) VALUES (?, ?, ?);` +var insertUserQuery = `INSERT INTO users (username, password, role, avatar_id) +VALUES (?, ?, ?, ?);` + +var countUserQuery = `SELECT count(*) FROM users;` + +var countAdminQuery = `SELECT count(*) FROM users WHERE role= 1;` + +var getUserQuery = `SELECT id, username, role, avatar_id FROM users WHERE username = ? AND password = ? LIMIT 1;` + +var getAnonymousUserQuery = `SELECT id, username, role, avatar_id FROM users WHERE role = 0 LIMIT 1;` + type Stmt struct { initFilesTable *sql.Stmt initFoldersTable *sql.Stmt @@ -179,6 +193,11 @@ type Stmt struct { getFilesInFolder *sql.Stmt getRandomFiles *sql.Stmt insertFeedback *sql.Stmt + insertUser *sql.Stmt + countUser *sql.Stmt + countAdmin *sql.Stmt + getUser *sql.Stmt + getAnonymousUser *sql.Stmt } func NewPreparedStatement(sqlConn *sql.DB) (*Stmt, error) { @@ -386,5 +405,48 @@ func NewPreparedStatement(sqlConn *sql.DB) (*Stmt, error) { return nil, err } + // init insertUser + stmt.insertUser, err = sqlConn.Prepare(insertUserQuery) + if err != nil { + return nil, err + } + + // init countUser + stmt.countUser, err = sqlConn.Prepare(countUserQuery) + if err != nil { + return nil, err + } + + // init countAdmin + stmt.countAdmin, err = sqlConn.Prepare(countAdminQuery) + if err != nil { + return nil, err + } + + // init getUser + stmt.getUser, err = sqlConn.Prepare(getUserQuery) + if err != nil { + return nil, err + } + + // init getAnonymousUser + stmt.getAnonymousUser, err = sqlConn.Prepare(getAnonymousUserQuery) + if err != nil { + return nil, err + } + + // insert Anonymous user if users is empty + userCount := 0 + err = stmt.countUser.QueryRow().Scan(&userCount) + if err != nil { + return nil, err + } + if userCount == 0 { + _, err = stmt.insertUser.Exec("Anonymous user", "", 0, 0) + if err != nil { + return nil, err + } + } + return stmt, err } diff --git a/pkg/database/struct.go b/pkg/database/struct.go index 8abf8c0..f4c56dc 100644 --- a/pkg/database/struct.go +++ b/pkg/database/struct.go @@ -20,6 +20,14 @@ type Folder struct { Foldername string `json:"foldername"` } +type User struct { + ID int64 `json:"id"` + Username string `json:"username"` + Password string `json:"-"` + Role int64 `json:"role"` + AvatarId int64 `json:"avatar_id"` +} + func (f *File) Path() (string, error) { folder, err := f.Db.GetFolder(f.Folder_id) if err != nil { @@ -27,4 +35,3 @@ func (f *File) Path() (string, error) { } return filepath.Join(folder.Folder, f.Filename), nil } - diff --git a/web/src/App.js b/web/src/App.js index 2896642..afc4b5a 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -1,9 +1,4 @@ -import { - HashRouter as Router, - Routes, - Route, - NavLink, -} from "react-router-dom"; +import { HashRouter as Router, Routes, Route, NavLink } from "react-router-dom"; import "./App.css"; import GetRandomFiles from "./component/GetRandomFiles"; @@ -12,11 +7,15 @@ import SearchFolders from "./component/SearchFolders"; import FilesInFolder from "./component/FilesInFolder"; import Manage from "./component/Manage"; import Share from "./component/Share"; +import Login from "./component/Login"; +import Register from "./component/Register"; import AudioPlayer from "./component/AudioPlayer"; +import UserStatus from "./component/UserStatus"; import { useState } from "react"; function App() { const [playingFile, setPlayingFile] = useState({}); + const [user, setUser] = useState({}); return (