diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml index bfc25c5..29878b4 100644 --- a/.gitea/workflows/build.yml +++ b/.gitea/workflows/build.yml @@ -104,7 +104,7 @@ jobs: export GOMODCACHE=/data/gomodcache mkdir -p $GOCACHE $GOMODCACHE - OUTPUT="../server-${{ matrix.goos }}-${{ matrix.arch }}${{ matrix.ext }}" + OUTPUT="../memberApp-${{ matrix.goos }}-${{ matrix.arch }}${{ matrix.ext }}" if [ "${{ matrix.arch }}" = "arm" ]; then GOARM=${{ matrix.arm_version }} fi diff --git a/.gitignore b/.gitignore index a44f7fe..50f974d 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ yarn-error.log* # golang quasar websever executable backend/server-linux-arm64 +backend/cert diff --git a/backend/bin/dist/allowOrigins.json b/backend/bin/dist/allowOrigins.json deleted file mode 100644 index 152ffd2..0000000 --- a/backend/bin/dist/allowOrigins.json +++ /dev/null @@ -1 +0,0 @@ -["http://localhost:9000", "http://localhost:9500", "http://127.0.0.1:9500"] \ No newline at end of file diff --git a/backend/dbRequest/dbRequest.go b/backend/dbRequest/dbRequest.go index ce0a72f..76afc8f 100644 --- a/backend/dbRequest/dbRequest.go +++ b/backend/dbRequest/dbRequest.go @@ -8,19 +8,29 @@ import ( "github.com/gin-gonic/gin" ) -var DBCreate string = `CREATE TABLE IF NOT EXISTS users ( +var CreateUserTable string = `CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL, + email TEXT NOT NULL, role TEXT NOT NULL, password TEXT NOT NULL, settings TEXT NOT NULL );` -var DBNewUser string = `INSERT INTO users (username, role, password, settings) VALUES (?, ?, ?, ?)` -var DBQueryPassword string = `SELECT role, password, settings FROM users WHERE username = ?` +var CreateRoleTable string = `CREATE TABLE IF NOT EXISTS roles ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + role TEXT NOT NULL, + rights TEXT NOT NULL + );` + +var NewUser string = `INSERT INTO users (username, email, role, password, settings) VALUES (?, ?, ?, ?, ?)` +var NewRole = `INSERT INTO roles (role, rights) VALUES (?, ?)` +var DBQueryPassword string = `SELECT id, role, password, settings FROM users WHERE username = ?` var DBUserLookup string = `SELECT EXISTS(SELECT 1 FROM users WHERE username = ?)` +var DBRoleLookup string = `SELECT EXISTS(SELECT 1 FROM roles WHERE role = ?)` var DBRemoveUser string = `DELETE FROM users WHERE username = $1` var DBUpdateSettings string = `UPDATE users SET settings = ? WHERE username = ?` +var DBUpdateRole string = `UPDATE roles SET rights = ? WHERE role = ?` func CheckDBError(c *gin.Context, username string, err error) bool { if err != nil { diff --git a/backend/main.go b/backend/main.go index 9933c1b..b40d8ae 100644 --- a/backend/main.go +++ b/backend/main.go @@ -14,6 +14,8 @@ import ( "strings" "time" + "gitea.tecamino.com/paadi/tecamino-dbm/cert" + dbApi "gitea.tecamino.com/paadi/memberDB/api" "gitea.tecamino.com/paadi/tecamino-logger/logging" "github.com/gin-contrib/cors" @@ -28,13 +30,16 @@ func main() { spa := flag.String("spa", "./dist/spa", "quasar spa files") workingDir := flag.String("workingDirectory", ".", "quasar spa files") ip := flag.String("ip", "0.0.0.0", "server listening ip") + organization := flag.String("organization", "", "self signed ciertificate organization") port := flag.Uint("port", 9500, "server listening port") + https := flag.Bool("https", false, "serves as https needs flag -cert and -chain") + sslCert := flag.String("cert", "", "ssl certificate path") + sslChain := flag.String("chain", "", "ssl chain path") debug := flag.Bool("debug", false, "log debug") flag.Parse() //change working directory only if value is given if *workingDir != "." && *workingDir != "" { - fmt.Println(1, *workingDir) os.Chdir(*workingDir) } @@ -72,13 +77,18 @@ func main() { dbHandler := dbApi.NewAPIHandler() //get local ip - allowOrigins = append(allowOrigins, "http://localhost:9000", "http://localhost:9500", "http://127.0.0.1:9500") + httpString := "http://" + if *https { + httpString = "https://" + + } + allowOrigins = append(allowOrigins, httpString+"localhost:9000", httpString+"localhost:9500", httpString+"127.0.0.1:9500") localIP, err := utils.GetLocalIP() if err != nil { logger.Error("main", fmt.Sprintf("get local ip : %s", err.Error())) } else { - allowOrigins = append(allowOrigins, fmt.Sprintf("http://%s:9000", localIP), fmt.Sprintf("http://%s:9500", localIP)) + allowOrigins = append(allowOrigins, fmt.Sprintf("%s%s:9000", httpString, localIP), fmt.Sprintf("%s%s:9500", httpString, localIP)) } s.Routes.Use(cors.New(cors.Config{ @@ -102,18 +112,26 @@ func main() { //private auth := api.Group("/secure", user.AuthMiddleware()) + auth.GET("/users", userManager.GetUserById) auth.GET("/members", dbHandler.GetMemberById) + auth.GET("/roles", userManager.GetRoleById) auth.POST("database/open", dbHandler.OpenDatabase) auth.POST("/members/add", dbHandler.AddNewMember) auth.POST("/members/edit", dbHandler.EditMember) auth.POST("/members/delete", dbHandler.DeleteMember) auth.POST("/members/import/csv", dbHandler.ImportCSV) - auth.POST("/settings/update", userManager.UpdateSettings) - auth.POST("/user/add", userManager.AddUser) - auth.POST("/login/refresh", userManager.Refresh) - auth.DELETE("/user", userManager.RemoveUser) + auth.POST("/settings/update", userManager.UpdateSettings) + + auth.POST("/roles/add", userManager.AddRole) + auth.POST("/roles/update", userManager.UpdateRole) + auth.POST("/roles/delete", userManager.DeleteRole) + + auth.POST("/users/add", userManager.AddUser) + auth.POST("/users/delete", userManager.DeleteUser) + + auth.POST("/login/refresh", userManager.Refresh) // Serve static files s.Routes.StaticFS("/assets", gin.Dir(filepath.Join(*spa, "assets"), true)) @@ -136,10 +154,29 @@ func main() { go func() { time.Sleep(500 * time.Millisecond) - if err := utils.OpenBrowser(fmt.Sprintf("http://localhost:%d", *port), logger); err != nil { + if err := utils.OpenBrowser(fmt.Sprintf("%slocalhost:%d", httpString, *port), logger); err != nil { logger.Error("main", fmt.Sprintf("starting browser error : %s", err.Error())) } }() + + if *https { + if *sslCert == "" { + logger.Error("ssl certificate", "-cert flag not given for https server") + log.Fatal("-cert flag not given for https server") + } + if *sslChain == "" { + logger.Error("ssl key", "-chain flag not given for https server") + log.Fatal("-chain flag not given for https server") + } + + // start https server + logger.Info("main", fmt.Sprintf("https listen on ip: %s port: %d", *ip, *port)) + if err := s.ServeHttps(*ip, *port, cert.Cert{Organization: *organization, CertFile: *sslCert, KeyFile: *sslChain}); err != nil { + logger.Error("main", "error https server "+err.Error()) + } + return + } + // start http server logger.Info("main", fmt.Sprintf("http listen on ip: %s port: %d", *ip, *port)) if err := s.ServeHttp(*ip, *port); err != nil { diff --git a/backend/members.dba b/backend/members.dba index 7861e82..eb0c588 100644 Binary files a/backend/members.dba and b/backend/members.dba differ diff --git a/backend/models/rights.go b/backend/models/rights.go new file mode 100644 index 0000000..70fecfa --- /dev/null +++ b/backend/models/rights.go @@ -0,0 +1,8 @@ +package models + +type Rights struct { + Name string `json:"name,omitempty"` + Read bool `json:"read,omitempty"` + Write bool `json:"write,omitempty"` + Delete bool `json:"delete,omitempty"` +} diff --git a/backend/models/role.go b/backend/models/role.go new file mode 100644 index 0000000..7a99145 --- /dev/null +++ b/backend/models/role.go @@ -0,0 +1,11 @@ +package models + +type Role struct { + Id int `json:"id"` + Role string `json:"role"` + Rights []Rights `json:"rights"` +} + +func (r *Role) IsValid() bool { + return r.Role != "" +} diff --git a/backend/models/user.go b/backend/models/user.go index 621e819..54a52cc 100644 --- a/backend/models/user.go +++ b/backend/models/user.go @@ -1,7 +1,9 @@ package models type User struct { + Id int `json:"id"` Name string `json:"user"` + Email string `json:"email"` Role string `json:"role"` Password string `json:"password,omitempty"` Settings Settings `json:"settings"` diff --git a/backend/server/server.go b/backend/server/server.go index 5595c5e..8c46c3a 100644 --- a/backend/server/server.go +++ b/backend/server/server.go @@ -29,10 +29,10 @@ func (s *Server) ServeHttp(ip string, port uint) error { } // serve dbm as http -func (s *Server) ServeHttps(port uint, cert cert.Cert) error { +func (s *Server) ServeHttps(url string, port uint, cert cert.Cert) error { // generate self signed tls certificate if err := cert.GenerateSelfSignedCert(); err != nil { return err } - return s.Routes.RunTLS(fmt.Sprintf(":%d", port), cert.CertFile, cert.KeyFile) + return s.Routes.RunTLS(fmt.Sprintf("%s:%d", url, port), cert.CertFile, cert.KeyFile) } diff --git a/backend/undefined b/backend/undefined deleted file mode 100644 index d2d7528..0000000 Binary files a/backend/undefined and /dev/null differ diff --git a/backend/user/login.go b/backend/user/login.go index 60befa5..99f096a 100644 --- a/backend/user/login.go +++ b/backend/user/login.go @@ -4,9 +4,7 @@ import ( "backend/dbRequest" "backend/models" "backend/utils" - "database/sql" "encoding/json" - "fmt" "io" "net/http" "time" @@ -21,101 +19,11 @@ var DOMAIN = "localhost" var ACCESS_TOKEN_TIME = 15 * time.Minute var REFRESH_TOKEN_TIME = 72 * time.Hour -func (um *UserManager) AddUser(c *gin.Context) { - body, err := io.ReadAll(c.Request.Body) - if err != nil { - c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) - return - } - - user := models.User{} - err = json.Unmarshal(body, &user) - if err != nil { - c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) - return - } - - if !user.IsValid() { - c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse("user empty")) - return - } - - db, err := sql.Open(um.dbType, um.dbFile) - if dbRequest.CheckDBError(c, user.Name, err) { - return - } - defer db.Close() - - var exists bool - - if err := db.QueryRow(dbRequest.DBUserLookup, user.Name).Scan(&exists); dbRequest.CheckDBError(c, user.Name, err) { - return - } - - if exists { - c.JSON(http.StatusOK, models.NewJsonErrorMessageResponse(fmt.Sprintf("user '%s' exists already", user.Name))) - return - } - - hash, err := utils.HashPassword(user.Password) - if err != nil { - c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) - return - } - if _, err := db.Exec(dbRequest.DBNewUser, user.Role, user.Name, hash, "{}"); dbRequest.CheckDBError(c, user.Name, err) { - return - } - - c.JSON(http.StatusOK, gin.H{ - "message": fmt.Sprintf("user '%s' successfully added", user.Name), - }) -} - -func (um *UserManager) RemoveUser(c *gin.Context) { - body, err := io.ReadAll(c.Request.Body) - if err != nil { - c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) - return - } - - user := models.User{} - err = json.Unmarshal(body, &user) - if err != nil { - c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) - return - } - - if !user.IsValid() { - c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse("user empty")) - return - } - - db, err := sql.Open(um.dbType, um.dbFile) - if dbRequest.CheckDBError(c, user.Name, err) { - return - } - defer db.Close() - - var storedPassword string - if err := db.QueryRow(dbRequest.DBQueryPassword, user.Name).Scan(&storedPassword, &user.Role); dbRequest.CheckDBError(c, user.Name, err) { - return - } - - if !utils.CheckPassword(user.Password, storedPassword) { - c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse("wrong password")) - return - } - - if _, err := db.Exec(dbRequest.DBRemoveUser, user.Name); dbRequest.CheckDBError(c, user.Name, err) { - return - } - - c.JSON(http.StatusOK, gin.H{ - "message": fmt.Sprintf("user '%s' successfully removed", user.Name), - }) -} - func (um *UserManager) Login(c *gin.Context) { + if !um.databaseOpened(c) { + return + } + body, err := io.ReadAll(c.Request.Body) if err != nil { c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) @@ -134,14 +42,8 @@ func (um *UserManager) Login(c *gin.Context) { return } - db, err := sql.Open(um.dbType, um.dbFile) - if dbRequest.CheckDBError(c, user.Name, err) { - return - } - defer db.Close() - var storedPassword, settingsJsonString string - if err := db.QueryRow(dbRequest.DBQueryPassword, user.Name).Scan(&user.Role, &storedPassword, &settingsJsonString); dbRequest.CheckDBError(c, user.Name, err) { + if err := um.database.QueryRow(dbRequest.DBQueryPassword, user.Name).Scan(&user.Id, &user.Role, &storedPassword, &settingsJsonString); dbRequest.CheckDBError(c, user.Name, err) { return } @@ -162,6 +64,7 @@ func (um *UserManager) Login(c *gin.Context) { // Create token accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "id": user.Id, "username": user.Name, "role": user.Role, "type": "access", @@ -169,6 +72,7 @@ func (um *UserManager) Login(c *gin.Context) { }) refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "id": user.Id, "username": user.Name, "role": user.Role, "type": "refresh", @@ -194,9 +98,9 @@ func (um *UserManager) Login(c *gin.Context) { c.SetCookie("refresh_token", refreshString, int(time.Until(refreshTokenExp).Seconds()), "/", "", secure, true) - fmt.Println(22, user.Settings) c.JSON(http.StatusOK, gin.H{ "message": "login successful", + "id": user.Id, "user": user.Name, "role": user.Role, "settings": user.Settings, @@ -225,11 +129,13 @@ func (um *UserManager) Refresh(c *gin.Context) { } username := claims["username"].(string) + id := claims["id"].(float64) role := claims["role"].(string) // new access token accessExp := time.Now().Add(ACCESS_TOKEN_TIME) newAccess := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "id": id, "username": username, "role": role, "exp": accessExp.Unix(), @@ -259,6 +165,7 @@ func (um *UserManager) Me(c *gin.Context) { claims := token.Claims.(jwt.MapClaims) c.JSON(http.StatusOK, gin.H{ + "id": claims["id"], "user": claims["username"], "role": claims["role"], }) diff --git a/backend/user/manager.go b/backend/user/manager.go index e966998..c653d30 100644 --- a/backend/user/manager.go +++ b/backend/user/manager.go @@ -2,15 +2,23 @@ package user import ( "backend/dbRequest" + "backend/models" "backend/utils" "database/sql" + "encoding/json" + "errors" "fmt" + "io" + "net/http" "os" + "strconv" + "strings" + + "github.com/gin-gonic/gin" ) type UserManager struct { - dbType string - dbFile string + database *sql.DB } func NewUserManager(dir string) (*UserManager, error) { @@ -18,17 +26,17 @@ func NewUserManager(dir string) (*UserManager, error) { dir = "." } - var typ string = "sqlite" - var file string = fmt.Sprintf("%s/user.db", dir) + var err error + var um UserManager + file := fmt.Sprintf("%s/user.db", dir) + + um.database, err = sql.Open("sqlite", file) + if err != nil { + return nil, err + } if _, err := os.Stat(file); err != nil { - db, err := sql.Open(typ, file) - if err != nil { - return nil, err - } - defer db.Close() - - _, err = db.Exec(dbRequest.DBCreate) + _, err = um.database.Exec(dbRequest.CreateUserTable) if err != nil { return nil, err } @@ -37,13 +45,217 @@ func NewUserManager(dir string) (*UserManager, error) { if err != nil { return nil, err } - _, err = db.Exec(dbRequest.DBNewUser, "admin", "admin", hash, "{}") + _, err = um.database.Exec(dbRequest.NewUser, "admin", "", "admin", hash, `{"databaseName":"members.dba","primaryColor":"#1976d2", "secondaryColor":"#26a69a"}`) if err != nil { return nil, err } } - return &UserManager{ - dbType: typ, - dbFile: file, - }, nil + return &um, nil +} + +func (um *UserManager) databaseOpened(c *gin.Context) bool { + if um.database == nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": "no database opened", + }) + return false + } + return true +} + +func (um *UserManager) AddUser(c *gin.Context) { + if !um.databaseOpened(c) { + return + } + + body, err := io.ReadAll(c.Request.Body) + if err != nil { + c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) + return + } + + user := models.User{} + err = json.Unmarshal(body, &user) + if err != nil { + c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) + return + } + + if !user.IsValid() { + c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse("user empty")) + return + } + + var exists bool + + if err := um.database.QueryRow(dbRequest.DBUserLookup, user.Name).Scan(&exists); dbRequest.CheckDBError(c, user.Name, err) { + return + } + + if exists { + c.JSON(http.StatusOK, models.NewJsonErrorMessageResponse(fmt.Sprintf("user '%s' exists already", user.Name))) + return + } + + hash, err := utils.HashPassword(user.Password) + if err != nil { + c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) + return + } + + if !utils.IsValidEmail(user.Email) { + c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(errors.New("not valid email address"))) + return + } + + if _, err := um.database.Exec(dbRequest.NewUser, user.Name, user.Email, user.Role, hash, "{}"); dbRequest.CheckDBError(c, user.Name, err) { + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": fmt.Sprintf("user '%s' successfully added", user.Name), + }) +} + +func (um *UserManager) GetUserById(c *gin.Context) { + if !um.databaseOpened(c) { + return + } + + var i int + var err error + + id := c.Query("id") + if id != "" { + i, err = strconv.Atoi(id) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + } + + query := `SELECT id, username, email, role, settings FROM users` + var args any + if i > 0 { + query = ` + SELECT id, username, email, role, settings FROM users + WHERE id = ? + ` + args = i + } + + rows, err := um.database.Query(query, args) + if err != nil { + return + } + defer rows.Close() + + var users []models.User + + for rows.Next() { + var id int + var name, email, role, settingsString string + if err = rows.Scan(&id, &name, &email, &role, &settingsString); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + var settings models.Settings + err := json.Unmarshal([]byte(settingsString), &settings) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + users = append(users, models.User{ + Id: id, + Name: name, + Email: email, + Role: role, + Settings: settings, + }) + } + + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, users) +} + +func (um *UserManager) DeleteUser(c *gin.Context) { + if !um.databaseOpened(c) { + return + } + + queryId := c.Query("id") + + if queryId == "" || queryId == "null" || queryId == "undefined" { + c.JSON(http.StatusBadRequest, gin.H{ + "message": "id query missing or wrong value: " + queryId, + }) + return + } + + var request struct { + Ids []int `json:"ids"` + } + + err := c.BindJSON(&request) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + if len(request.Ids) == 0 { + c.JSON(http.StatusBadRequest, gin.H{ + "message": "no ids given to be deleted", + }) + return + } + + var ownId string + placeholders := make([]string, len(request.Ids)) + args := make([]any, len(request.Ids)) + for i, id := range request.Ids { + if queryId == fmt.Sprint(id) { + ownId = queryId + continue + } + + placeholders[i] = "?" + args[i] = id + } + + query := fmt.Sprintf("DELETE FROM users WHERE id IN (%s)", strings.Join(placeholders, ",")) + + _, err = um.database.Exec(query, args...) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + if ownId != "" { + c.JSON(http.StatusBadRequest, gin.H{ + "message": "can not delete logged in member id: " + queryId, + "id": queryId, + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "member(s) deleted", + }) } diff --git a/backend/user/roles.go b/backend/user/roles.go new file mode 100644 index 0000000..c677f4d --- /dev/null +++ b/backend/user/roles.go @@ -0,0 +1,245 @@ +package user + +import ( + "backend/dbRequest" + "backend/models" + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + "strings" + + "github.com/gin-gonic/gin" +) + +func (um *UserManager) AddRole(c *gin.Context) { + if !um.databaseOpened(c) { + return + } + + body, err := io.ReadAll(c.Request.Body) + if err != nil { + c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) + return + } + + role := models.Role{} + err = json.Unmarshal(body, &role) + if err != nil { + c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) + return + } + + if !role.IsValid() { + c.JSON(http.StatusBadRequest, models.NewJsonErrorMessageResponse("user empty")) + return + } + + var exists bool + + if err := um.database.QueryRow(dbRequest.DBRoleLookup, role.Role).Scan(&exists); dbRequest.CheckDBError(c, role.Role, err) { + return + } + + if exists { + c.JSON(http.StatusOK, models.NewJsonErrorMessageResponse(fmt.Sprintf("role '%s' exists already", role.Role))) + return + } + + jsonBytes, err := json.Marshal(role.Rights) + if err != nil { + c.JSON(http.StatusOK, models.NewJsonErrorMessageResponse(err.Error())) + return + } + + if _, err := um.database.Exec(dbRequest.NewRole, role.Role, string(jsonBytes)); dbRequest.CheckDBError(c, role.Role, err) { + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": fmt.Sprintf("role '%s' successfully added", role.Role), + }) +} + +func (um *UserManager) GetRoleById(c *gin.Context) { + if !um.databaseOpened(c) { + return + } + + if _, err := um.database.Exec(dbRequest.CreateRoleTable); err != nil { + c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) + return + } + + var i int + var err error + + id := c.Query("id") + if id != "" { + i, err = strconv.Atoi(id) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + } + + query := `SELECT id, role, rights FROM roles` + var args any + if i > 0 { + query = ` + SELECT id, role, rights FROM users + WHERE id = ? + ` + args = i + } + + rows, err := um.database.Query(query, args) + if err != nil { + return + } + defer rows.Close() + + var roles []models.Role + + for rows.Next() { + var id int + var role, rightsString string + if err = rows.Scan(&id, &role, &rightsString); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + var data struct { + Rights []models.Rights `json:"rights"` + } + err := json.Unmarshal([]byte(rightsString), &data) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + roles = append(roles, models.Role{ + Id: id, + Role: role, + Rights: data.Rights, + }) + } + + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, roles) +} + +func (um *UserManager) UpdateRole(c *gin.Context) { + if !um.databaseOpened(c) { + return + } + + body, err := io.ReadAll(c.Request.Body) + if err != nil { + c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) + return + } + + role := models.Role{} + err = json.Unmarshal(body, &role) + if err != nil { + c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) + return + } + + jsonBytes, err := json.Marshal(role) + if err != nil { + c.JSON(http.StatusOK, models.NewJsonErrorMessageResponse(err.Error())) + return + } + + if _, err := um.database.Exec(dbRequest.DBUpdateRole, string(jsonBytes), role.Role); err != nil { + c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": fmt.Sprintf("role rights '%s' successfully updated", role.Role), + }) +} + +func (um *UserManager) DeleteRole(c *gin.Context) { + if !um.databaseOpened(c) { + return + } + + queryRole := c.Query("role") + + if queryRole == "" || queryRole == "null" || queryRole == "undefined" { + c.JSON(http.StatusBadRequest, gin.H{ + "message": "role query missing or wrong value: " + queryRole, + }) + return + } + + var request struct { + Roles []string `json:"roles"` + } + + err := c.BindJSON(&request) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + if len(request.Roles) == 0 { + c.JSON(http.StatusBadRequest, gin.H{ + "message": "no roles given to be deleted", + }) + return + } + + var ownRole string + placeholders := make([]string, len(request.Roles)) + args := make([]any, len(request.Roles)) + for i, role := range request.Roles { + if ownRole == role { + ownRole = queryRole + continue + } + + placeholders[i] = "?" + args[i] = role + } + + query := fmt.Sprintf("DELETE FROM roles WHERE role IN (%s)", strings.Join(placeholders, ",")) + + _, err = um.database.Exec(query, args...) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + if ownRole != "" { + c.JSON(http.StatusBadRequest, gin.H{ + "message": "can not delete logged in role id: " + ownRole, + "role": ownRole, + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "role(s) deleted", + }) +} diff --git a/backend/user/settings.go b/backend/user/settings.go index 3c7cce1..ee20561 100644 --- a/backend/user/settings.go +++ b/backend/user/settings.go @@ -3,7 +3,6 @@ package user import ( "backend/dbRequest" "backend/models" - "database/sql" "encoding/json" "fmt" "io" @@ -13,6 +12,10 @@ import ( ) func (um *UserManager) UpdateSettings(c *gin.Context) { + if !um.databaseOpened(c) { + return + } + body, err := io.ReadAll(c.Request.Body) if err != nil { c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) @@ -26,19 +29,13 @@ func (um *UserManager) UpdateSettings(c *gin.Context) { return } - db, err := sql.Open(um.dbType, um.dbFile) - if dbRequest.CheckDBError(c, user.Name, err) { - return - } - defer db.Close() - jsonBytes, err := json.Marshal(user.Settings) if err != nil { c.JSON(http.StatusOK, models.NewJsonErrorMessageResponse(err.Error())) return } - if _, err := db.Exec(dbRequest.DBUpdateSettings, string(jsonBytes), user.Name); err != nil { + if _, err := um.database.Exec(dbRequest.DBUpdateSettings, string(jsonBytes), user.Name); err != nil { c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) return } diff --git a/backend/utils/utils.go b/backend/utils/utils.go index 8acad74..21370a1 100644 --- a/backend/utils/utils.go +++ b/backend/utils/utils.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "runtime" "gitea.tecamino.com/paadi/tecamino-logger/logging" @@ -63,3 +64,8 @@ func FindAllFiles(rootDir, fileExtention string) (files []string, err error) { }) return } + +func IsValidEmail(email string) bool { + re := regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`) + return re.MatchString(email) +} diff --git a/eslint.config.js b/eslint.config.js index 20e7947..3830a34 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,9 +1,9 @@ -import js from '@eslint/js' -import globals from 'globals' -import pluginVue from 'eslint-plugin-vue' -import pluginQuasar from '@quasar/app-vite/eslint' -import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript' -import prettierSkipFormatting from '@vue/eslint-config-prettier/skip-formatting' +import js from '@eslint/js'; +import globals from 'globals'; +import pluginVue from 'eslint-plugin-vue'; +import pluginQuasar from '@quasar/app-vite/eslint'; +import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'; +import prettierSkipFormatting from '@vue/eslint-config-prettier/skip-formatting'; export default defineConfigWithVueTs( { @@ -33,16 +33,13 @@ export default defineConfigWithVueTs( * pluginVue.configs["flat/recommended"] * -> Above, plus rules to enforce subjective community defaults to ensure consistency. */ - pluginVue.configs[ 'flat/essential' ], + pluginVue.configs['flat/essential'], { - files: ['**/*.ts', '**/*.vue'], + files: ['**/*.ts', '**/*.d.ts', '**/*.vue'], rules: { - '@typescript-eslint/consistent-type-imports': [ - 'error', - { prefer: 'type-imports' } - ], - } + '@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }], + }, }, // https://github.com/vuejs/eslint-config-typescript vueTsConfigs.recommendedTypeChecked, @@ -60,8 +57,8 @@ export default defineConfigWithVueTs( cordova: 'readonly', Capacitor: 'readonly', chrome: 'readonly', // BEX related - browser: 'readonly' // BEX related - } + browser: 'readonly', // BEX related + }, }, // add your custom rules here @@ -69,18 +66,18 @@ export default defineConfigWithVueTs( 'prefer-promise-reject-errors': 'off', // allow debugger during development only - 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' - } + 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', + }, }, { - files: [ 'src-pwa/custom-service-worker.ts' ], + files: ['src-pwa/custom-service-worker.ts'], languageOptions: { globals: { - ...globals.serviceworker - } - } + ...globals.serviceworker, + }, + }, }, - prettierSkipFormatting -) + prettierSkipFormatting, +); diff --git a/package-lock.json b/package-lock.json index 2bfc4b2..fb8d593 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "lightcontrol", - "version": "0.1.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "lightcontrol", - "version": "0.1.0", + "version": "1.0.0", "hasInstallScript": true, "dependencies": { "@capacitor-community/sqlite": "^7.0.1", @@ -18,7 +18,8 @@ "quasar": "^2.16.0", "vue": "^3.4.18", "vue-i18n": "^11.1.12", - "vue-router": "^4.0.12" + "vue-router": "^4.0.12", + "vuedraggable": "^4.1.0" }, "devDependencies": { "@eslint/js": "^9.14.0", @@ -7280,6 +7281,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sortablejs": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/sortablejs/-/sortablejs-1.14.0.tgz", + "integrity": "sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==", + "license": "MIT" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -8215,6 +8222,18 @@ "typescript": ">=5.0.0" } }, + "node_modules/vuedraggable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-4.1.0.tgz", + "integrity": "sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==", + "license": "MIT", + "dependencies": { + "sortablejs": "1.14.0" + }, + "peerDependencies": { + "vue": "^3.0.1" + } + }, "node_modules/wcwidth": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", diff --git a/package.json b/package.json index 8d94c2a..7f7a5b9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lightcontrol", - "version": "1.0.0", + "version": "1.0.1", "description": "A Tecamino App", "productName": "Member Database", "author": "A. Zuercher", @@ -24,7 +24,8 @@ "quasar": "^2.16.0", "vue": "^3.4.18", "vue-i18n": "^11.1.12", - "vue-router": "^4.0.12" + "vue-router": "^4.0.12", + "vuedraggable": "^4.1.0" }, "devDependencies": { "@eslint/js": "^9.14.0", diff --git a/quasar.config.ts b/quasar.config.ts index 1fdbf16..e445758 100644 --- a/quasar.config.ts +++ b/quasar.config.ts @@ -81,7 +81,7 @@ export default defineConfig((/* ctx */) => { // Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-file#devserver devServer: { open: true, // opens browser window automatically - https: false, // or true if you want HTTPS + https: true, // or true if you want HTTPS port: 9000, // your custom port // host: '0.0.0.0', // allows external access (not just localhost) // allowedHosts: ['members.tecamino.com'], diff --git a/src/assets/lang/de-CH.yaml b/src/assets/lang/de-CH.yaml index 895102b..eff15da 100644 --- a/src/assets/lang/de-CH.yaml +++ b/src/assets/lang/de-CH.yaml @@ -1,6 +1,6 @@ language: Sprach prename: Vorname -lastname: Nachname +lastName: Nachname birthday: Geburtstag email: Email group: Gruppe @@ -46,3 +46,17 @@ resetColors: Farbe zrügsetze save: Spichere users: Benutzer roles: Rollen +name: Name +role: Rolle +addNewUser: Füeg neue Benutzer hinzue +expires: Ablouf +selectUserOptions: Wähle Benutzer Optione +prenameIsRequired: Vorname ist erforderlich +lastNameIsRequired: Nachname ist erforderlich +birthdayIsRequired: Geburtstag ist erforderlich +userIsRequired: Benutzer ist erforderlich +emailIsRequired: Email ist erforderlich +roleIsRequired: Rolle ist erforderlich +rights: Recht +selectRoleOptions: Wähle Roue Optione +addNewRole: Füeg neui Roue hinzue diff --git a/src/assets/lang/de-DE.yaml b/src/assets/lang/de-DE.yaml index 5b93069..59e2f1e 100644 --- a/src/assets/lang/de-DE.yaml +++ b/src/assets/lang/de-DE.yaml @@ -1,6 +1,6 @@ language: Sprache prename: Vorname -lastname: Nachname +lastName: Nachname birthday: Geburtstag email: Email group: Gruppe @@ -46,3 +46,17 @@ resetColors: Farben zurücksetzen save: Speichern users: Benutzer roles: Rollen +name: Name +role: Rolle +addNewUser: Füge neuen Benutzer hinzu +expires: Ablauf +selectUserOptions: Wähle Benutzer Optionen +prenameIsRequired: Vorname ist erforderlich +lastNameIsRequired: Nachname ist erforderlich +birthdayIsRequired: Geburtstag ist erforderlich +userIsRequired: Benutzer ist erforderlich +emailIsRequired: Email ist erforderlich +roleIsRequired: Rolle ist erforderlich +rights: Rechte +selectRoleOptions: Wähle Rollen Option +addNewRole: Füge neue Rolle hinzu diff --git a/src/assets/lang/en-US.yaml b/src/assets/lang/en-US.yaml index 42c1181..9dfca59 100644 --- a/src/assets/lang/en-US.yaml +++ b/src/assets/lang/en-US.yaml @@ -1,6 +1,6 @@ language: Language prename: Prename -lastname: Name +lastName: Name birthday: Birthday email: Email group: Group @@ -46,3 +46,17 @@ resetColors: Reset Colors save: Save users: Users roles: Roles +name: Name +role: Role +addNewUser: Add new User +expires: Expires +selectUserOptions: Select User Options +prenameIsRequired: Fist Name is required +lastNameIsRequired: Last Name is required +birthdayIsRequired: Birthday is required +userIsRequired: User is required +emailIsRequired: Email is required +roleIsRequired: Role is required +rights: Rights +selectRoleOptions: Select Role Options +addNewRole: Add new Role diff --git a/src/boot/auth.ts b/src/boot/auth.ts index 6891582..e864967 100644 --- a/src/boot/auth.ts +++ b/src/boot/auth.ts @@ -14,7 +14,7 @@ export default boot(async ({ app }) => { await appApi .get('/login/me') .then((resp) => { - useStore.setUser({ username: resp.data.username, role: resp.data.role }); + useStore.setUser({ id: resp.data.id, username: resp.data.username, role: resp.data.role }); login.refresh().catch((err) => console.error(err)); }) .catch(() => { diff --git a/src/boot/axios.ts b/src/boot/axios.ts index 063fa5d..fe58472 100644 --- a/src/boot/axios.ts +++ b/src/boot/axios.ts @@ -8,7 +8,7 @@ export const portApp = 9500; // Create axios instance export const appApi: AxiosInstance = axios.create({ - baseURL: `http://${host}:${portApp}/api`, + baseURL: `https://${host}:${portApp}/api`, timeout: 10000, withCredentials: true, }); diff --git a/src/boot/quasar-global.ts b/src/boot/quasar-global.ts index b4e574f..249af1f 100644 --- a/src/boot/quasar-global.ts +++ b/src/boot/quasar-global.ts @@ -9,11 +9,17 @@ export default boot(({ app, router }) => { const $q = app.config.globalProperties.$q; setQuasarInstance($q); - Logo.value = localStorage.getItem('icon') ?? ''; - databaseName.value = localStorage.getItem('databaseName') ?? ''; - document.documentElement.style.setProperty('--q-primary', localStorage.getItem('primaryColor')); - document.documentElement.style.setProperty( - '--q-secondary', - localStorage.getItem('secondaryColor'), - ); + Logo.value = localStorage.getItem('icon') ?? Logo.value; + databaseName.value = localStorage.getItem('databaseName') ?? databaseName.value; + let primaryColor = localStorage.getItem('primaryColor'); + if (primaryColor == null || primaryColor === 'undefined' || primaryColor.trim() === '') { + primaryColor = null; + } + let secondaryColor = localStorage.getItem('secondaryColor'); + if (secondaryColor == null || secondaryColor === 'undefined' || secondaryColor.trim() === '') { + secondaryColor = null; + } + + document.documentElement.style.setProperty('--q-primary', primaryColor ?? '#1976d2'); + document.documentElement.style.setProperty('--q-secondary', secondaryColor ?? '#26a69a'); }); diff --git a/src/components/EditAllDialog.vue b/src/components/EditAllDialog.vue deleted file mode 100644 index 27bb67c..0000000 --- a/src/components/EditAllDialog.vue +++ /dev/null @@ -1,139 +0,0 @@ - - - diff --git a/src/components/EditOneDialog.vue b/src/components/EditOneDialog.vue index c3a5934..c719bbf 100644 --- a/src/components/EditOneDialog.vue +++ b/src/components/EditOneDialog.vue @@ -27,9 +27,9 @@ const dialog = ref(); const localMember = ref(); const localTitle = ref(''); const localField = ref(''); -const value = ref(); +const value = ref(''); -const emit = defineEmits(['update-member']); +const emit = defineEmits(['update']); const { NotifyResponse } = useNotify(); function open(label: string, field: string, member: Member) { @@ -37,44 +37,7 @@ function open(label: string, field: string, member: Member) { localField.value = field; localMember.value = member; - switch (field) { - case 'firstName': - value.value = member.firstName; - break; - case 'lastName': - value.value = member.lastName; - break; - case 'birthday': - value.value = member.birthday; - break; - case 'address': - value.value = member.address; - break; - case 'town': - value.value = member.town; - break; - case 'zip': - value.value = member.zip; - break; - case 'phone': - value.value = member.phone; - break; - case 'email': - value.value = member.email; - break; - case 'group': - value.value = member.group; - break; - case 'responsiblePerson': - value.value = member.responsiblePerson; - break; - case 'firstVisit': - value.value = member.firstVisit; - break; - case 'lastVisit': - value.value = member.lastVisit; - break; - } + value.value = localMember.value[field]; dialog.value?.open(); } @@ -82,120 +45,18 @@ function save() { const query = 'secure/members/edit?id=' + localMember.value.id; let payload = {}; - switch (localField.value) { - case 'firstName': - if (value.value === localMember.value.firstName) { - dialog.value.close(); - return; - } - payload = { - firstName: value.value, - }; - break; - case 'lastName': - if (value.value === localMember.value.lastName) { - dialog.value.close(); - return; - } - payload = { - lastName: value.value, - }; - break; - case 'birthday': - if (value.value === localMember.value.birthday) { - dialog.value.close(); - return; - } - payload = { - birthday: value.value, - }; - break; - case 'address': - if (value.value === localMember.value.address) { - dialog.value.close(); - return; - } - payload = { - address: value.value, - }; - break; - case 'town': - if (value.value === localMember.value.town) { - dialog.value.close(); - return; - } - payload = { - town: value.value, - }; - break; - case 'zip': - if (value.value === localMember.value.zip) { - dialog.value.close(); - return; - } - payload = { - zip: value.value, - }; - break; - case 'phone': - if (value.value === localMember.value.phone) { - dialog.value.close(); - return; - } - payload = { - phone: value.value, - }; - break; - case 'email': - if (value.value === localMember.value.email) { - dialog.value.close(); - return; - } - payload = { - email: value.value, - }; - break; - case 'group': - if (value.value === localMember.value.group) { - dialog.value.close(); - return; - } - payload = { - group: value.value, - }; - break; - case 'responsiblePerson': - if (value.value === localMember.value.responsiblePerson) { - dialog.value.close(); - return; - } - payload = { - responsiblePerson: value.value, - }; - break; - case 'firstVisit': - if (value.value === localMember.value.firstVisit) { - dialog.value.close(); - return; - } - payload = { - firstVisit: value.value, - }; - break; - case 'lastVisit': - if (value.value === localMember.value.lastVisit) { - dialog.value.close(); - return; - } - payload = { - lastVisit: value.value, - }; - break; + if (value.value === localMember.value[localField.value]) { + dialog.value.close(); + return; } + payload = { + [localField.value]: value.value, + }; + appApi .post(query, payload) .then(() => { - emit('update-member', ''); + emit('update', ''); dialog.value.close(); }) .catch((err) => { diff --git a/src/components/MemberEditAllDialog.vue b/src/components/MemberEditAllDialog.vue new file mode 100644 index 0000000..628d909 --- /dev/null +++ b/src/components/MemberEditAllDialog.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/src/components/RoleEditAllDialog.vue b/src/components/RoleEditAllDialog.vue new file mode 100644 index 0000000..4ab489b --- /dev/null +++ b/src/components/RoleEditAllDialog.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/src/components/UploadDialog.vue b/src/components/UploadDialog.vue index 3d56e71..f0d7423 100644 --- a/src/components/UploadDialog.vue +++ b/src/components/UploadDialog.vue @@ -4,7 +4,7 @@
+ +
+ + + + +
+
+ Save +
+
+ + + + + diff --git a/src/pages/UserSettings.vue b/src/pages/UserSettings.vue index 28ca04e..ed92fcf 100644 --- a/src/pages/UserSettings.vue +++ b/src/pages/UserSettings.vue @@ -14,32 +14,25 @@ - + - + - +
- - {{ $t('save') }} diff --git a/src/vueLib/login/useLogin.ts b/src/vueLib/login/useLogin.ts index 55c0c80..363f93f 100644 --- a/src/vueLib/login/useLogin.ts +++ b/src/vueLib/login/useLogin.ts @@ -26,7 +26,7 @@ export function useLogin() { }); const resp = await appApi.get('/login/me'); - userStore.setUser({ username: resp.data.user, role: resp.data.role }); + userStore.setUser({ id: resp.data.id, username: resp.data.user, role: resp.data.role }); startRefreshInterval(); return true; @@ -52,7 +52,7 @@ export function useLogin() { appApi .get('/login/me') .then((resp) => { - userStore.setUser({ username: resp.data.user, role: resp.data.role }); + userStore.setUser({ id: resp.data.id, username: resp.data.user, role: resp.data.role }); if (!intervalId) { startRefreshInterval(); } diff --git a/src/vueLib/login/userStore.ts b/src/vueLib/login/userStore.ts index 3121a7a..5cb33ad 100644 --- a/src/vueLib/login/userStore.ts +++ b/src/vueLib/login/userStore.ts @@ -3,6 +3,7 @@ import { useGlobalRouter } from 'src/utils/globalRouter'; import { useGlobalQ } from 'src/utils/globalQ'; interface User { + id: number; username: string; role: string; } diff --git a/src/vueLib/models/rights.ts b/src/vueLib/models/rights.ts new file mode 100644 index 0000000..be2d30c --- /dev/null +++ b/src/vueLib/models/rights.ts @@ -0,0 +1,8 @@ +export interface Right { + name: string; + read: boolean; + write: boolean; + delete: boolean; +} + +export type Rights = Right[]; diff --git a/src/vueLib/models/roles.ts b/src/vueLib/models/roles.ts new file mode 100644 index 0000000..0e37ddb --- /dev/null +++ b/src/vueLib/models/roles.ts @@ -0,0 +1,9 @@ +import type { Rights } from './rights'; + +export interface Role { + id?: number; + role: string; + rights: Rights | null; +} + +export type Roles = Role[]; diff --git a/src/vueLib/models/users.ts b/src/vueLib/models/users.ts index b5abab5..f8cb525 100644 --- a/src/vueLib/models/users.ts +++ b/src/vueLib/models/users.ts @@ -1,6 +1,7 @@ export interface User { - id: number; - name: string; + id?: number; + user: string; + email: string; role: string; expires: string; } diff --git a/src/vueLib/tables/members/MembersTable.ts b/src/vueLib/tables/members/MembersTable.ts index 25010e4..bfeeb01 100644 --- a/src/vueLib/tables/members/MembersTable.ts +++ b/src/vueLib/tables/members/MembersTable.ts @@ -28,7 +28,7 @@ export function useMemberTable() { { name: 'lastName', align: 'left' as const, - label: i18n.global.t('lastname'), + label: i18n.global.t('lastName'), field: 'lastName', sortable: true, }, @@ -196,6 +196,7 @@ export function useMemberTable() { loading.value = false; }); } + return { members, pagination, diff --git a/src/vueLib/tables/members/MembersTable.vue b/src/vueLib/tables/members/MembersTable.vue index 75f6488..01a99f9 100644 --- a/src/vueLib/tables/members/MembersTable.vue +++ b/src/vueLib/tables/members/MembersTable.vue @@ -104,8 +104,8 @@ - - + + ([]); + +export function useRoleTable() { + const pagination = ref({ + sortBy: 'role', + descending: false, + page: 1, + rowsPerPage: 10, + }); + + const columns = computed(() => [ + { + name: 'id', + align: 'left' as const, + label: 'Id', + field: 'id', + sortable: true, + style: 'width: 50px; max-width: 50px;', + }, + { + name: 'role', + align: 'left' as const, + label: i18n.global.t('role'), + field: 'role', + sortable: true, + }, + { + name: 'rights', + align: 'left' as const, + label: i18n.global.t('rights'), + field: 'rights', + sortable: true, + style: 'width: 120px; max-width: 120px;', + }, + { name: 'option', align: 'center' as const, label: '', field: 'option', icon: 'option' }, + ]); + + const { NotifyResponse } = useNotify(); + + const loading = ref(false); + + //updates user list from database + function updateRoles() { + loading.value = true; + appApi + .get('secure/roles') + .then((resp) => { + if (resp.data === null) { + roles.value = []; + return; + } + roles.value = resp.data as Roles; + if (roles.value === null) { + roles.value = []; + return; + } + }) + + .catch((err) => { + NotifyResponse(err, 'error'); + }) + .finally(() => { + loading.value = false; + }); + } + return { + roles, + pagination, + columns, + loading, + updateRoles, + }; +} diff --git a/src/vueLib/tables/roles/RoleTable.vue b/src/vueLib/tables/roles/RoleTable.vue new file mode 100644 index 0000000..baccc97 --- /dev/null +++ b/src/vueLib/tables/roles/RoleTable.vue @@ -0,0 +1,201 @@ + + + + + diff --git a/src/vueLib/tables/users/UserTable.ts b/src/vueLib/tables/users/UserTable.ts index 3555e3d..76eab67 100644 --- a/src/vueLib/tables/users/UserTable.ts +++ b/src/vueLib/tables/users/UserTable.ts @@ -8,68 +8,26 @@ export function useUserTable() { const users = ref([]); const pagination = ref({ - sortBy: 'firstName', + sortBy: 'user', descending: false, page: 1, rowsPerPage: 10, }); const columns = computed(() => [ - { name: 'cake', align: 'center' as const, label: '', field: 'cake', icon: 'cake' }, { - name: 'firstName', + name: 'id', align: 'left' as const, - label: i18n.global.t('prename'), - field: 'firstName', + label: 'Id', + field: 'id', sortable: true, + style: 'width: 50px; max-width: 50px;', }, { - name: 'lastName', + name: 'user', align: 'left' as const, - label: i18n.global.t('lastname'), - field: 'lastName', - sortable: true, - }, - { - name: 'birthday', - align: 'left' as const, - label: i18n.global.t('birthday'), - field: 'birthday', - sortable: true, - }, - { - name: 'age', - align: 'left' as const, - label: i18n.global.t('age'), - field: 'age', - sortable: true, - }, - { - name: 'address', - align: 'left' as const, - label: i18n.global.t('address'), - field: 'address', - sortable: true, - }, - { - name: 'town', - align: 'left' as const, - label: i18n.global.t('town'), - field: 'town', - sortable: true, - }, - { - name: 'zip', - align: 'left' as const, - label: i18n.global.t('zipCode'), - field: 'zip', - sortable: true, - }, - { - name: 'phone', - align: 'left' as const, - label: i18n.global.t('phone'), - field: 'phone', + label: i18n.global.t('user'), + field: 'user', sortable: true, }, { @@ -80,32 +38,12 @@ export function useUserTable() { sortable: true, }, { - name: 'group', + name: 'role', align: 'left' as const, - label: i18n.global.t('group'), - field: 'group', - sortable: true, - }, - { - name: 'responsiblePerson', - align: 'left' as const, - label: i18n.global.t('responsible'), - field: 'responsiblePerson', - sortable: true, - }, - { - name: 'firstVisit', - align: 'left' as const, - label: i18n.global.t('firstVisit'), - field: 'firstVisit', - sortable: true, - }, - { - name: 'lastVisit', - align: 'left' as const, - label: i18n.global.t('lastVisit'), - field: 'lastVisit', + label: i18n.global.t('role'), + field: 'role', sortable: true, + style: 'width: 120px; max-width: 120px;', }, { name: 'option', align: 'center' as const, label: '', field: 'option', icon: 'option' }, ]); @@ -119,7 +57,7 @@ export function useUserTable() { loading.value = true; appApi - .get('users') + .get('secure/users') .then((resp) => { if (resp.data === null) { users.value = []; diff --git a/src/vueLib/tables/users/VueTable.vue b/src/vueLib/tables/users/UserTable.vue similarity index 72% rename from src/vueLib/tables/users/VueTable.vue rename to src/vueLib/tables/users/UserTable.vue index 0ed010a..71cacdd 100644 --- a/src/vueLib/tables/users/VueTable.vue +++ b/src/vueLib/tables/users/UserTable.vue @@ -35,9 +35,6 @@ > {{ $t('selectUserOptions') }} - - {{ $t('importCSV') }} -
@@ -63,7 +60,11 @@
- - + + -