From 090cf0c1cc2a9079c16f69996895b9ce04c2589d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Z=C3=BCrcher?= Date: Fri, 24 Oct 2025 16:09:01 +0200 Subject: [PATCH] add api handler --- accessHandler.go | 64 +++++----- accessHandlerAPI.go | 158 +++++++++++++++++++++++ api.go | 305 ++++++++++++++++++++++++++++++++++++++++++++ dbHandler.go | 302 ------------------------------------------- go.mod | 5 +- go.sum | 2 + models/role.go | 4 + role.go | 22 ++-- user.go | 22 ++-- utils/utils.go | 8 ++ 10 files changed, 535 insertions(+), 357 deletions(-) create mode 100644 accessHandlerAPI.go create mode 100644 api.go delete mode 100644 dbHandler.go create mode 100644 utils/utils.go diff --git a/accessHandler.go b/accessHandler.go index fb69d49..437b4dc 100644 --- a/accessHandler.go +++ b/accessHandler.go @@ -1,38 +1,40 @@ package AccessHandler -import "gitea.tecamino.com/paadi/tecamino-logger/logging" +import ( + "gitea.tecamino.com/paadi/dbHandler" + "gitea.tecamino.com/paadi/tecamino-logger/logging" +) -// // AccessHandler // // Description: -// AccessHandler manages access-related functionality, including -// database operations for users and roles, as well as logging. -// It encapsulates a database handler and a logger so that -// authentication and authorization operations can be performed -// consistently across the application. // +// AccessHandler manages access-related functionality, including +// database operations for users and roles, as well as logging. +// It encapsulates a database handler and a logger so that +// authentication and authorization operations can be performed +// consistently across the application. type AccessHandler struct { - dbHandler *DBHandler // Database handler used for managing users and roles - logger *logging.Logger // Centralized application logger + dbHandler *dbHandler.DBHandler // Database handler used for managing users and roles + logger *logging.Logger // Centralized application logger } -// // NewAccessHandler // // Description: -// Creates and initializes a new AccessHandler instance. +// +// Creates and initializes a new AccessHandler instance. // // Behavior: -// 1. If a logger is not provided (nil), it creates a new logger instance -// that writes to "accessHandler.log". -// 2. Initializes the AccessHandler struct. -// 3. Sets up the internal DBHandler with the same logger. -// 4. Automatically creates required database tables and default data: -// - User table -// - Default user(s) -// - Role table -// - Default role(s) +// 1. If a logger is not provided (nil), it creates a new logger instance +// that writes to "accessHandler.log". +// 2. Initializes the AccessHandler struct. +// 3. Sets up the internal DBAHandler with the same logger. +// 4. Automatically creates required database tables and default data: +// - User table +// - Default user(s) +// - Role table +// - Default role(s) // // Parameters: // - dbPath: The file path to the database. @@ -43,11 +45,11 @@ type AccessHandler struct { // - err: Any error that occurs during initialization. // // Example: -// handler, err := NewAccessHandler("data/app.db", appLogger) -// if err != nil { -// log.Fatal(err) -// } // +// handler, err := NewAccessHandler("data/app.db", appLogger) +// if err != nil { +// log.Fatal(err) +// } func NewAccessHandler(path string, logger *logging.Logger) (aH *AccessHandler, err error) { if logger == nil { logger, err = logging.NewLogger("accessHandler.log", nil) @@ -65,7 +67,7 @@ func NewAccessHandler(path string, logger *logging.Logger) (aH *AccessHandler, e logger.Debug("NewAccessHandler", "initialize db handler") // Create a new DB handler instance - aH.dbHandler, err = NewDBHandler(path, logger) + aH.dbHandler, err = dbHandler.NewDBHandler(path, "user", logger) if err != nil { aH.logger.Error("NewAccessHandler", err) return @@ -105,21 +107,21 @@ func NewAccessHandler(path string, logger *logging.Logger) (aH *AccessHandler, e return } -// // GetLogger // // Description: -// Returns the logger associated with this AccessHandler instance. -// Useful when another component or handler needs to reuse the -// same logging instance for consistent log output. +// +// Returns the logger associated with this AccessHandler instance. +// Useful when another component or handler needs to reuse the +// same logging instance for consistent log output. // // Returns: // - *logging.Logger: The logger assigned to this AccessHandler. // // Example: -// log := accessHandler.GetLogger() -// log.Info("Some event") // +// log := accessHandler.GetLogger() +// log.Info("Some event") func (aH *AccessHandler) GetLogger() *logging.Logger { return aH.logger } diff --git a/accessHandlerAPI.go b/accessHandlerAPI.go new file mode 100644 index 0000000..61a0d4e --- /dev/null +++ b/accessHandlerAPI.go @@ -0,0 +1,158 @@ +package AccessHandler + +import ( + "gitea.tecamino.com/paadi/access-handler/models" + "gitea.tecamino.com/paadi/dbHandler" + "gitea.tecamino.com/paadi/tecamino-logger/logging" +) + +// AccessHandler +// +// Description: +// +// AccessHandler manages access-related functionality, including +// database operations for users and roles, as well as logging. +// It encapsulates a database handler and a logger so that +// authentication and authorization operations can be performed +// consistently across the application. +type AccessHandlerAPI struct { + dbHandler *dbHandler.DBHandler // Database handler used for managing users and roles + logger *logging.Logger // Centralized application logger +} + +// NewAccessHandlerAPI +// +// Description: +// +// Creates and initializes a new AccessHandler instance. +// +// Behavior: +// 1. If a logger is not provided (nil), it creates a new logger instance +// that writes to "accessHandler.log". +// 2. Initializes the AccessHandler struct. +// 3. Sets up the internal DBAHandler with the same logger. +// 4. Automatically creates required database tables and default data: +// - User table +// - Default user(s) +// - Role table +// - Default role(s) +// +// Parameters: +// - dbPath: The file path to the database. +// - logger: Optional pointer to a logging.Logger instance. If nil, a new one is created. +// +// Returns: +// - aH: A pointer to the fully initialized AccessHandler. +// - err: Any error that occurs during initialization. +// +// Example: +// +// handler, err := NewAccessHandlerAPI("data/app.db", appLogger) +// if err != nil { +// log.Fatal(err) +// } +func NewAccessHandlerAPIAPI(path string, logger *logging.Logger) (aH *AccessHandlerAPI, err error) { + if logger == nil { + logger, err = logging.NewLogger("accessHandler.log", nil) + if err != nil { + return + } + } + + logger.Debug("NewAccessHandlerAPI", "initialize new access handler") + + // Initialize AccessHandler with logger + aH = &AccessHandlerAPI{ + logger: logger, + } + + logger.Debug("NewAccessHandlerAPI", "initialize db handler") + // Create a new DB handler instance + aH.dbHandler, err = dbHandler.NewDBHandler(path, "user", logger) + if err != nil { + aH.logger.Error("NewAccessHandlerAPI", err) + return + } + + logger.Debug("NewAccessHandlerAPI", "add user table") + // Add the user table to the database + err = aH.dbHandler.AddNewTable(models.User{}) + if err != nil { + aH.logger.Error("NewAccessHandlerAPI", err) + return + } + + logger.Debug("NewAccessHandlerAPI", "add default user") + // Add default users to the system + name := "admin" + role := "admin" + email := "zuercher@tecamino.ch" + + // Check if a user with this email already exists + if err := aH.dbHandler.Exists(&models.User{}, "email", email, false); err == nil { + aH.logger.Debug("AddDefaultUser", "user email "+email+" exists already") + // Found a user → skip create + } else { + // Create default settings for the new user + settings := models.Settings{} + aH.logger.Debug("AddDefaultUser", "set default quasar settings") + settings.DefaultQuasarSettings() + + // Insert default admin user into the database + aH.dbHandler.AddNewColum(&models.User{ + Name: name, + Role: role, + Email: email, + Password: "$2a$10$sZZOWBP8DSFLrLFQNoXw8OsEEr0tez1B8lPzKCHofaHg6PMNxx1pG", + Settings: settings, + }) + } + + logger.Debug("NewAccessHandlerAPI", "add role table") + // Add the role table to the database + err = aH.dbHandler.AddNewTable(models.Role{}) + if err != nil { + aH.logger.Error("NewAccessHandlerAPI", err) + return + } + + // Add default roles to the system + logger.Debug("NewAccessHandlerAPI", "add default role") + + // Check if a role with this name already exists + if err := aH.dbHandler.Exists(&models.Role{}, "role", role, false); err == nil { + // Found a role → skip creation + aH.logger.Debug("AddDefaultRole", "role "+role+" exists already") + } else { + // Initialize default permissions for admin + permissions := models.Permissions{} + aH.logger.Debug("AddDefaultRole", "set default Permissions") + permissions.DefaultPermissions() + + // Create the default admin role + aH.dbHandler.AddNewColum(&models.Role{ + Role: role, + Permissions: permissions, + }) + } + return +} + +// GetLogger +// +// Description: +// +// Returns the logger associated with this AccessHandler instance. +// Useful when another component or handler needs to reuse the +// same logging instance for consistent log output. +// +// Returns: +// - *logging.Logger: The logger assigned to this AccessHandler. +// +// Example: +// +// log := accessHandler.GetLogger() +// log.Info("Some event") +func (aH *AccessHandlerAPI) GetLogger() *logging.Logger { + return aH.logger +} diff --git a/api.go b/api.go new file mode 100644 index 0000000..b673708 --- /dev/null +++ b/api.go @@ -0,0 +1,305 @@ +package AccessHandler + +import ( + "errors" + "fmt" + "net/http" + "strconv" + + "gitea.tecamino.com/paadi/access-handler/models" + "gitea.tecamino.com/paadi/access-handler/utils" + "github.com/gin-gonic/gin" +) + +func (aH *AccessHandlerAPI) AddUser(c *gin.Context) { + var user models.User + err := c.BindJSON(&user) + if err != nil { + aH.logger.Error("AddUser", err) + c.JSON(http.StatusInternalServerError, models.NewJsonErrorResponse(err)) + return + } + + if !user.IsValid() { + aH.logger.Error("AddUser", "user empty") + c.JSON(http.StatusBadRequest, models.NewJsonMessageResponse("user empty")) + return + } + + // Check if a user with this email already exists + if err := aH.dbHandler.Exists(&models.User{}, "email", user.Email, false); err == nil { + // Found a user → skip create + aH.logger.Error("AddUser", "user with email "+user.Email+" already exists") + c.JSON(http.StatusBadRequest, models.NewJsonMessageResponse(fmt.Sprintf("user with email %s already exists", user.Email))) + return + } + + hash, err := utils.HashPassword(user.Password) + if err != nil { + aH.logger.Error("AddUser", err) + c.JSON(http.StatusInternalServerError, nil) + return + } + + if !utils.IsValidEmail(user.Email) { + aH.logger.Error("AddUser", "not valid email address") + c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(errors.New("not valid email address"))) + return + } + + // Hash the provided password before saving + hash, err = utils.HashPassword(user.Password) + if err != nil { + aH.logger.Error("AddUser", err) + c.JSON(http.StatusInternalServerError, nil) + return + } + + aH.logger.Debug("AddUser", "add new user "+user.Name+" with role "+user.Role) + + // Insert the new user record + aH.dbHandler.AddNewColum(&models.User{ + Name: user.Name, + Role: user.Role, + Email: user.Email, + Password: hash, + }) + + c.JSON(http.StatusOK, gin.H{ + "message": fmt.Sprintf("user '%s' successfully added", user.Name), + }) +} + +func (aH *AccessHandlerAPI) GetUser(c *gin.Context) { + var i int + var err error + + id := c.Query("id") + if id != "" { + i, err = strconv.Atoi(id) + if err != nil { + aH.logger.Error("GetUser", err) + c.JSON(http.StatusInternalServerError, nil) + return + } + } + + var users []models.User + err = aH.dbHandler.GetById(&users, uint(i)) + if err != nil { + aH.logger.Error("GetUser", err) + c.JSON(http.StatusInternalServerError, nil) + return + } + c.JSON(http.StatusOK, users) +} + +func (aH *AccessHandlerAPI) UpdateUser(c *gin.Context) { + var user models.User + if err := c.BindJSON(&user); err != nil { + aH.logger.Error("UpdateUser", err) + c.JSON(http.StatusInternalServerError, nil) + return + } + err := aH.dbHandler.UpdateValuesById(&user, user.Id) + if err != nil { + aH.logger.Error("UpdateUser", err) + c.JSON(http.StatusInternalServerError, nil) + return + } + c.JSON(http.StatusOK, models.NewJsonMessageResponse("successfully updated user '"+user.Email+"'")) + +} + +func (aH *AccessHandlerAPI) DeleteUser(c *gin.Context) { + queryId := c.Query("id") + + if queryId == "" || queryId == "null" || queryId == "undefined" { + aH.logger.Error("DeleteUser", "id query missing or wrong value: "+queryId) + 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 { + aH.logger.Error("DeleteUser", "id query missing or wrong value: "+queryId) + c.JSON(http.StatusInternalServerError, nil) + return + } + + if len(request.Ids) == 0 { + aH.logger.Error("DeleteUser", "no ids given to be deleted") + c.JSON(http.StatusBadRequest, gin.H{ + "message": "no ids given to be deleted", + }) + return + } + + var ownId string + removeIds := make([]uint, len(request.Ids)) + for i, id := range request.Ids { + if queryId == fmt.Sprint(id) { + ownId = queryId + continue + } + removeIds[i] = uint(id) + } + + if ownId != "" { + aH.logger.Error("DeleteUser", "can not delete logged in member id: "+queryId) + c.JSON(http.StatusBadRequest, gin.H{ + "message": "can not delete logged in member id: " + queryId, + "id": queryId, + }) + return + } + + err = aH.dbHandler.DeleteById(&models.User{}, removeIds...) + if err != nil { + aH.logger.Error("DeleteUser", err) + c.JSON(http.StatusInternalServerError, nil) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "member(s) deleted", + }) +} + +func (aH *AccessHandlerAPI) AddRole(c *gin.Context) { + var role models.Role + err := c.BindJSON(&role) + if err != nil { + aH.logger.Error("AddRole", err) + c.JSON(http.StatusBadRequest, models.NewJsonErrorResponse(err)) + return + } + + if !role.IsValid() { + aH.logger.Error("AddRole", "user empty") + c.JSON(http.StatusBadRequest, models.NewJsonMessageResponse("user empty")) + return + } + + // Check if a role with this name already exists + if err := aH.dbHandler.Exists(&models.Role{}, "role", role, false); err == nil { + aH.logger.Error("AddRole", fmt.Sprintf("role with name %s already exists", role.Role)) + c.JSON(http.StatusBadRequest, models.NewJsonMessageResponse(fmt.Sprintf("role with name %s already exists", role.Role))) + } + + // Insert new role with provided permissions + aH.dbHandler.AddNewColum(&models.Role{ + Role: role.Role, + Permissions: role.Permissions, + }) + c.JSON(http.StatusOK, gin.H{ + "message": fmt.Sprintf("role '%s' successfully added", role.Role), + }) +} + +func (aH *AccessHandlerAPI) GetRole(c *gin.Context) { + var i int + var err error + + id := c.Query("id") + if id != "" { + i, err = strconv.Atoi(id) + if err != nil { + aH.logger.Error("GetRole", err) + c.JSON(http.StatusInternalServerError, nil) + return + } + } + + var role []models.Role + err = aH.dbHandler.GetById(&role, uint(i)) + if err != nil { + aH.logger.Error("GetRole", err) + c.JSON(http.StatusInternalServerError, nil) + return + } + c.JSON(http.StatusOK, role) +} + +func (aH *AccessHandlerAPI) UpdateRole(c *gin.Context) { + var role models.Role + if err := c.BindJSON(&role); err != nil { + aH.logger.Error("UpdateRole", err) + c.JSON(http.StatusInternalServerError, nil) + return + } + err := aH.dbHandler.UpdateValuesById(&role, role.Id) + if err != nil { + aH.logger.Error("UpdateRole", err) + c.JSON(http.StatusInternalServerError, nil) + return + } + c.JSON(http.StatusOK, models.NewJsonMessageResponse("successfully updated role '"+role.Role+"'")) +} + +func (aH *AccessHandlerAPI) DeleteRole(c *gin.Context) { + queryId := c.Query("id") + + if queryId == "" || queryId == "null" || queryId == "undefined" { + aH.logger.Error("DeleteRole", "id query missing or wrong value: "+queryId) + 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 { + aH.logger.Error("DeleteRole", "id query missing or wrong value: "+queryId) + c.JSON(http.StatusInternalServerError, nil) + return + } + + if len(request.Ids) == 0 { + aH.logger.Error("DeleteRole", "no ids given to be deleted") + c.JSON(http.StatusBadRequest, gin.H{ + "message": "no ids given to be deleted", + }) + return + } + + var ownId string + removeIds := make([]uint, len(request.Ids)) + for i, id := range request.Ids { + if queryId == fmt.Sprint(id) { + ownId = queryId + continue + } + removeIds[i] = uint(id) + } + + if ownId != "" { + aH.logger.Error("DeleteRole", "can not delete logged in member role id: "+queryId) + c.JSON(http.StatusBadRequest, gin.H{ + "message": "can not delete logged in member id: " + queryId, + "id": queryId, + }) + return + } + + err = aH.dbHandler.DeleteById(&models.Role{}, removeIds...) + if err != nil { + aH.logger.Error("DeleteUser", err) + c.JSON(http.StatusInternalServerError, nil) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "role(s) deleted", + }) +} diff --git a/dbHandler.go b/dbHandler.go deleted file mode 100644 index 63079f9..0000000 --- a/dbHandler.go +++ /dev/null @@ -1,302 +0,0 @@ -package AccessHandler - -import ( - "errors" - "fmt" - "path/filepath" - "reflect" - "strings" - - "gitea.tecamino.com/paadi/tecamino-logger/logging" - "github.com/glebarez/sqlite" - "gorm.io/gorm" -) - -// DBHandler -// -// Description: -// -// Wraps the GORM database connection and provides helper methods for -// common CRUD operations, as well as integrated logging for traceability. -// -// Fields: -// - db: Active GORM database connection. -// - logger: Pointer to a custom logger instance for structured logging. -type DBHandler struct { - db *gorm.DB - logger *logging.Logger -} - -// NewDBHandler -// -// Description: -// -// Creates a new database handler using the specified SQLite database file. -// -// Behavior: -// 1. Opens a GORM connection to the database file at `dbPath`. -// 2. Wraps it in a `DBHandler` struct with logging support. -// -// Parameters: -// - dbPath: Path to the SQLite database file. -// - logger: Logging instance to record DB operations. -// -// Returns: -// - dH: A pointer to the initialized `DBHandler`. -// - err: Any error encountered during database connection. -func NewDBHandler(path string, logger *logging.Logger) (dH *DBHandler, err error) { - dH = &DBHandler{logger: logger} - dbPath := filepath.Join(path, "user.db") - logger.Debug("NewDBHandler", "open database "+dbPath) - dH.db, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) - return -} - -// addNewTable -// -// Description: -// -// Uses GORM’s `AutoMigrate` to create or update the database schema -// for the provided model type. -// -// Parameters: -// - model: Struct type representing the database table schema. -// -// Returns: -// - error: Any migration error encountered. -func (dH *DBHandler) addNewTable(model any) error { - return dH.db.AutoMigrate(&model) -} - -// addNewColum -// -// Description: -// -// Inserts a new record into the database table corresponding to `model`. -// -// Parameters: -// - model: Struct instance containing values to be inserted. -// -// Returns: -// - error: Any error encountered during record creation. -func (dH *DBHandler) addNewColum(model any) error { - return dH.db.Create(model).Error -} - -// getById -// -// Description: -// -// Retrieves a record (or all records) from a table by numeric ID. -// -// Behavior: -// - If `id == 0`, returns all records in the table. -// - Otherwise, fetches the record matching the given ID. -// -// Parameters: -// - model: Pointer to a slice or struct to store the result. -// - id: Numeric ID to query by. -// -// Returns: -// - error: Any query error or “not found” message. -func (dH *DBHandler) getById(model any, id uint) error { - dH.logger.Debug("getById", "find id "+fmt.Sprint(id)) - - if id == 0 { - return dH.db.Find(model).Error - } - - err := dH.db.First(model, id).Error - if errors.Is(err, gorm.ErrRecordNotFound) { - return fmt.Errorf("no record found for id: %v", id) - } else if err != nil { - return fmt.Errorf("query failed: %w", err) - } - return nil -} - -// getByKey -// -// Description: -// -// Retrieves one or more records matching a key/value pair. -// -// Behavior: -// - If `LikeSearch` is true, performs a SQL LIKE query. -// - Otherwise, performs an exact match query. -// -// Parameters: -// - model: Pointer to a slice or struct to store results. -// - key: Column name (e.g., "email"). -// - value: Value to match or partially match. -// - LikeSearch: If true, replaces '*' with '%' for wildcard matching. -// -// Returns: -// - error: Any database query error. -func (dH *DBHandler) getByKey(model any, key string, value any, LikeSearch bool) error { - if LikeSearch { - value = strings.ReplaceAll(fmt.Sprint(value), "*", "%") - dH.logger.Debug("getByKey", "find like key "+key+" value "+fmt.Sprint(value)) - return dH.db.Where(key+" LIKE ?", value).Find(model).Error - } - - dH.logger.Debug("getByKey", "find equal key "+key+" value "+fmt.Sprint(value)) - return dH.db.Find(model, key+" = ?", value).Error -} - -// updateValuesById -// -// Description: -// -// Updates record fields based on their unique ID. -// -// Behavior: -// 1. Confirms that `model` is a pointer to a struct. -// 2. Fetches the record by ID. -// 3. Updates all non-zero fields using `gorm.Model.Updates`. -// -// Parameters: -// - model: Pointer to struct containing new values. -// - id: Numeric ID of the record to update. -// -// Returns: -// - error: If the model is invalid or query/update fails. -func (dH *DBHandler) updateValuesById(model any, id uint) error { - dH.logger.Debug("updateValuesById", "model"+fmt.Sprint(model)) - modelType := reflect.TypeOf(model) - if modelType.Kind() != reflect.Ptr { - return errors.New("model must be a pointer to struct") - } - - lookUpModel := reflect.New(modelType.Elem()).Interface() - if err := dH.getById(lookUpModel, id); err != nil { - return err - } - return dH.db.Model(lookUpModel).Updates(model).Error -} - -// updateValuesByKey -// -// Description: -// -// Updates records based on a key/value match. -// -// Behavior: -// 1. Confirms model type. -// 2. Fetches the matching record(s) using `getByKey`. -// 3. Updates all non-zero fields. -// -// Parameters: -// - model: Pointer to struct containing updated values. -// - key: Column name to filter by. -// - value: Value to match. -// -// Returns: -// - error: Any query or update error. -func (dH *DBHandler) updateValuesByKey(model any, key string, value any) error { - dH.logger.Debug("updateValuesByKey", "model"+fmt.Sprint(model)) - modelType := reflect.TypeOf(model) - if modelType.Kind() != reflect.Ptr { - return errors.New("model must be a pointer to struct") - } - - lookUpModel := reflect.New(modelType.Elem()).Interface() - if err := dH.getByKey(lookUpModel, key, value, false); err != nil { - return err - } - return dH.db.Model(lookUpModel).Updates(model).Error -} - -// deleteById -// -// Description: -// -// Deletes records by their ID(s). -// -// Behavior: -// - If the first ID == 0, all records in the table are deleted. -// - Otherwise, deletes the provided IDs. -// -// Parameters: -// - model: Model struct type representing the table. -// - id: Variadic list of IDs to delete. -// -// Returns: -// - error: Any deletion error. -func (dH *DBHandler) deleteById(model any, id ...uint) error { - if id[0] == 0 { - dH.logger.Debug("deleteById", "delete all") - return dH.db.Where("1 = 1").Delete(model).Error - } - - dH.logger.Debug("deleteById", "delete ids"+fmt.Sprint(id)) - if err := dH.exists(model, "id", id, false); err != nil { - return err - } - return dH.db.Delete(model, id).Error -} - -// deleteByKey -// -// Description: -// -// Deletes records that match a key/value pair. -// -// Behavior: -// - Supports LIKE queries if `LikeSearch` is true. -// -// Parameters: -// - model: Model struct type representing the table. -// - key: Column name to filter by. -// - value: Value to match. -// - LikeSearch: Whether to use wildcard search. -// -// Returns: -// - error: Any deletion error. -func (dH *DBHandler) deleteByKey(model any, key string, value any, LikeSearch bool) error { - if LikeSearch { - value = strings.ReplaceAll(fmt.Sprint(value), "*", "%") - dH.logger.Debug("deleteByKey", "delete like key "+key+" value "+fmt.Sprint(value)) - return dH.db.Where(key+" LIKE ?", value).Delete(model).Error - } - - dH.logger.Debug("deleteByKey", "delete equal key "+key+" value "+fmt.Sprint(value)) - return dH.db.Where(key+" = ?", value).Delete(model).Error -} - -// exists -// -// Description: -// -// Checks whether a record exists matching the specified key/value filter. -// -// Behavior: -// - Performs a `First` query on the database. -// - If `LikeSearch` is true, performs a LIKE query. -// - Returns an error if the record does not exist or query fails. -// -// Parameters: -// - model: Model struct type to search. -// - key: Column name to filter by. -// - value: Value to match. -// - LikeSearch: Whether to use wildcard search. -// -// Returns: -// - error: “no record found” or DB error. -func (dH *DBHandler) exists(model any, key string, value any, LikeSearch bool) (err error) { - if LikeSearch { - value = strings.ReplaceAll(fmt.Sprint(value), "*", "%") - key = key + " LIKE ?" - } else { - key = key + " = ?" - } - - dH.logger.Debug("exists", "check if exists key "+key+" value "+fmt.Sprint(value)) - err = dH.db.Where(key, value).First(model).Error - if errors.Is(err, gorm.ErrRecordNotFound) { - return fmt.Errorf("no record found for %s %v", key[:len(key)-1], value) - } else if err != nil { - return fmt.Errorf("query failed: %w", err) - } - return -} diff --git a/go.mod b/go.mod index 90da8ce..2cb969e 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,12 @@ module gitea.tecamino.com/paadi/access-handler go 1.24.5 require ( + gitea.tecamino.com/paadi/dbHandler v1.0.2 gitea.tecamino.com/paadi/tecamino-logger v0.2.1 github.com/gin-gonic/gin v1.11.0 - github.com/glebarez/sqlite v1.11.0 github.com/go-playground/assert/v2 v2.2.0 github.com/golang-jwt/jwt/v5 v5.3.0 golang.org/x/crypto v0.43.0 - gorm.io/gorm v1.31.0 ) require ( @@ -20,6 +19,7 @@ require ( github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gin-contrib/sse v1.1.0 // indirect github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/glebarez/sqlite v1.11.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.27.0 // indirect @@ -52,6 +52,7 @@ require ( golang.org/x/tools v0.37.0 // indirect google.golang.org/protobuf v1.36.9 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gorm.io/gorm v1.31.0 // indirect modernc.org/libc v1.22.5 // indirect modernc.org/mathutil v1.5.0 // indirect modernc.org/memory v1.5.0 // indirect diff --git a/go.sum b/go.sum index 8392991..4d4ae62 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +gitea.tecamino.com/paadi/dbHandler v1.0.2 h1:5d3sWIFY6JQLX0PGSTsxcvZFCSNTgtx4lHTX5lwF2H8= +gitea.tecamino.com/paadi/dbHandler v1.0.2/go.mod h1:y/xn/POJg1DO++67uKvnO23lJQgh+XFQq7HZCS9Getw= gitea.tecamino.com/paadi/tecamino-logger v0.2.1 h1:sQTBKYPdzn9mmWX2JXZBtGBvNQH7cuXIwsl4TD0aMgE= gitea.tecamino.com/paadi/tecamino-logger v0.2.1/go.mod h1:FkzRTldUBBOd/iy2upycArDftSZ5trbsX5Ira5OzJgM= github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= diff --git a/models/role.go b/models/role.go index eec83ab..ff8badb 100644 --- a/models/role.go +++ b/models/role.go @@ -5,3 +5,7 @@ type Role struct { Role string `gorm:"column:role" json:"role"` Permissions Permissions `gorm:"type:json" json:"permissions"` } + +func (r *Role) IsValid() bool { + return r.Role != "" +} diff --git a/role.go b/role.go index ca42859..30ea9da 100644 --- a/role.go +++ b/role.go @@ -19,7 +19,7 @@ import ( // Returns: // - error: Any database error encountered. func (aH *AccessHandler) AddRoleTable() error { - return aH.dbHandler.addNewTable(models.Role{}) + return aH.dbHandler.AddNewTable(models.Role{}) } // AddDefaultRole @@ -45,7 +45,7 @@ func (aH *AccessHandler) AddDefaultRole() (err error) { role := "admin" // Check if a role with this name already exists - if err := aH.dbHandler.exists(&models.Role{}, "role", role, false); err == nil { + if err := aH.dbHandler.Exists(&models.Role{}, "role", role, false); err == nil { // Found a role → skip creation aH.logger.Debug("AddDefaultRole", "role "+role+" exists already") return nil @@ -57,7 +57,7 @@ func (aH *AccessHandler) AddDefaultRole() (err error) { permissions.DefaultPermissions() // Create the default admin role - aH.dbHandler.addNewColum(&models.Role{ + aH.dbHandler.AddNewColum(&models.Role{ Role: role, Permissions: permissions, }) @@ -82,13 +82,13 @@ func (aH *AccessHandler) AddDefaultRole() (err error) { // - error: If the role already exists or insertion fails. func (aH *AccessHandler) AddNewRole(role string, permissions models.Permissions) (err error) { // Check if a role with this name already exists - if err := aH.dbHandler.exists(&models.Role{}, "role", role, false); err == nil { + if err := aH.dbHandler.Exists(&models.Role{}, "role", role, false); err == nil { // Found a role → skip creation return fmt.Errorf("role with name %s already exists", role) } // Insert new role with provided permissions - aH.dbHandler.addNewColum(&models.Role{ + aH.dbHandler.AddNewColum(&models.Role{ Role: role, Permissions: permissions, }) @@ -108,7 +108,7 @@ func (aH *AccessHandler) AddNewRole(role string, permissions models.Permissions) // - roles: A slice containing the matched role (usually length 1). // - err: Any database error encountered. func (aH *AccessHandler) GetRoleById(id uint) (roles []models.Role, err error) { - err = aH.dbHandler.getById(&roles, id) + err = aH.dbHandler.GetById(&roles, id) return } @@ -127,7 +127,7 @@ func (aH *AccessHandler) GetRoleById(id uint) (roles []models.Role, err error) { // - roles: A list of matched roles. // - err: Any database error encountered. func (aH *AccessHandler) GetRoleByKey(key string, value any, likeSearch bool) (roles []models.Role, err error) { - err = aH.dbHandler.getByKey(&roles, key, value, likeSearch) + err = aH.dbHandler.GetByKey(&roles, key, value, likeSearch) return } @@ -144,7 +144,7 @@ func (aH *AccessHandler) GetRoleByKey(key string, value any, likeSearch bool) (r // Returns: // - error: Any database error encountered. func (aH *AccessHandler) UpdateRoleById(id uint, role models.Role) error { - return aH.dbHandler.updateValuesById(&role, id) + return aH.dbHandler.UpdateValuesById(&role, id) } // UpdateRoleByKey @@ -161,7 +161,7 @@ func (aH *AccessHandler) UpdateRoleById(id uint, role models.Role) error { // Returns: // - error: Any database error encountered. func (aH *AccessHandler) UpdateRoleByKey(role models.Role, key string, value any) error { - return aH.dbHandler.updateValuesByKey(&role, key, value) + return aH.dbHandler.UpdateValuesByKey(&role, key, value) } // DeleteRoleById @@ -176,7 +176,7 @@ func (aH *AccessHandler) UpdateRoleByKey(role models.Role, key string, value any // Returns: // - error: Any database error encountered during deletion. func (aH *AccessHandler) DeleteRoleById(id uint) (err error) { - return aH.dbHandler.deleteById(&models.Role{}, id) + return aH.dbHandler.DeleteById(&models.Role{}, id) } // DeleteRoleByKey @@ -193,5 +193,5 @@ func (aH *AccessHandler) DeleteRoleById(id uint) (err error) { // Returns: // - error: Any database error encountered. func (aH *AccessHandler) DeleteRoleByKey(key string, value any, likeSearch bool) (err error) { - return aH.dbHandler.deleteByKey(&models.Role{}, key, value, likeSearch) + return aH.dbHandler.DeleteByKey(&models.Role{}, key, value, likeSearch) } diff --git a/user.go b/user.go index 3fd2851..a21db73 100644 --- a/user.go +++ b/user.go @@ -20,7 +20,7 @@ import ( // Returns: // - error: Any database error that occurs while creating the table. func (aH *AccessHandler) AddUserTable() error { - return aH.dbHandler.addNewTable(models.User{}) + return aH.dbHandler.AddNewTable(models.User{}) } // AddDefaultUser @@ -49,7 +49,7 @@ func (aH *AccessHandler) AddDefaultUser() (err error) { email := "zuercher@tecamino.ch" // Check if a user with this email already exists - if err := aH.dbHandler.exists(&models.User{}, "email", email, false); err == nil { + if err := aH.dbHandler.Exists(&models.User{}, "email", email, false); err == nil { aH.logger.Debug("AddDefaultUser", "user email "+email+" exists already") // Found a user → skip create return nil @@ -61,7 +61,7 @@ func (aH *AccessHandler) AddDefaultUser() (err error) { settings.DefaultQuasarSettings() // Insert default admin user into the database - aH.dbHandler.addNewColum(&models.User{ + aH.dbHandler.AddNewColum(&models.User{ Name: name, Role: role, Email: email, @@ -92,7 +92,7 @@ func (aH *AccessHandler) AddDefaultUser() (err error) { // - error: If the user already exists or if hashing/insertion fails. func (aH *AccessHandler) AddNewUser(userName, email, password, role string) (err error) { // Check if a user with this email already exists - if err := aH.dbHandler.exists(&models.User{}, "email", email, false); err == nil { + if err := aH.dbHandler.Exists(&models.User{}, "email", email, false); err == nil { // Found a user → skip create aH.logger.Error("AddNewUser", "user with email "+email+" already exists") return fmt.Errorf("user with email %s already exists", email) @@ -107,7 +107,7 @@ func (aH *AccessHandler) AddNewUser(userName, email, password, role string) (err aH.logger.Debug("AddNewUser", "add new user "+userName+" with role "+role) // Insert the new user record - aH.dbHandler.addNewColum(&models.User{ + aH.dbHandler.AddNewColum(&models.User{ Name: userName, Role: role, Email: email, @@ -129,7 +129,7 @@ func (aH *AccessHandler) AddNewUser(userName, email, password, role string) (err // - users: A slice containing the matched user (usually length 1). // - err: Any database error encountered. func (aH *AccessHandler) GetUserById(id uint) (users []models.User, err error) { - err = aH.dbHandler.getById(&users, id) + err = aH.dbHandler.GetById(&users, id) return } @@ -148,7 +148,7 @@ func (aH *AccessHandler) GetUserById(id uint) (users []models.User, err error) { // - users: A list of users that match the search criteria. // - err: Any database error encountered. func (aH *AccessHandler) GetUserByKey(key string, value any, likeSearch bool) (users []models.User, err error) { - err = aH.dbHandler.getByKey(&users, key, value, likeSearch) + err = aH.dbHandler.GetByKey(&users, key, value, likeSearch) return } @@ -165,7 +165,7 @@ func (aH *AccessHandler) GetUserByKey(key string, value any, likeSearch bool) (u // Returns: // - error: Any error encountered during the update. func (aH *AccessHandler) UpdateUserById(id uint, user models.User) error { - return aH.dbHandler.updateValuesById(&user, id) + return aH.dbHandler.UpdateValuesById(&user, id) } // UpdateUserByKey @@ -182,7 +182,7 @@ func (aH *AccessHandler) UpdateUserById(id uint, user models.User) error { // Returns: // - error: Any error encountered during the update. func (aH *AccessHandler) UpdateUserByKey(user models.User, key string, value any) error { - return aH.dbHandler.updateValuesByKey(&user, key, value) + return aH.dbHandler.UpdateValuesByKey(&user, key, value) } // DeleteUserById @@ -197,7 +197,7 @@ func (aH *AccessHandler) UpdateUserByKey(user models.User, key string, value any // Returns: // - error: Any database error encountered during deletion. func (aH *AccessHandler) DeleteUserById(id uint) (err error) { - return aH.dbHandler.deleteById(&models.User{}, id) + return aH.dbHandler.DeleteById(&models.User{}, id) } // DeleteUserByKey @@ -214,5 +214,5 @@ func (aH *AccessHandler) DeleteUserById(id uint) (err error) { // Returns: // - error: Any database error encountered during deletion. func (aH *AccessHandler) DeleteUserByKey(key string, value any, likeSearch bool) (err error) { - return aH.dbHandler.deleteByKey(&models.User{}, key, value, likeSearch) + return aH.dbHandler.DeleteByKey(&models.User{}, key, value, likeSearch) } diff --git a/utils/utils.go b/utils/utils.go new file mode 100644 index 0000000..bc4818b --- /dev/null +++ b/utils/utils.go @@ -0,0 +1,8 @@ +package utils + +import "regexp" + +func IsValidEmail(email string) bool { + re := regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`) + return re.MatchString(email) +}