From 80675ed32806bd0314d539f66748912b7c6a1b73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Z=C3=BCrcher?= Date: Fri, 31 Oct 2025 08:11:07 +0100 Subject: [PATCH] implement dbhandler package and new test file --- api/api.go | 16 ++- api/apiHandler.go | 81 +++++++----- api/eventHandler.go | 74 ++++++----- api/membersHandler.go | 72 ++++++---- api/responsibleHandler.go | 36 ++--- data/mydatabase.db | Bin 32768 -> 0 bytes go.mod | 27 ++-- go.sum | 50 +++++-- handlers/database.go | 54 +++----- handlers/events.go | 169 +++++++---------------- handlers/members.go | 272 ++++++++++++-------------------------- handlers/responsible.go | 100 ++++---------- main.go | 9 +- member_test.go | 219 ++++++++++++++++++++++++++++++ models/event.go | 10 +- models/member.go | 29 ++-- models/person.go | 28 +++- 17 files changed, 674 insertions(+), 572 deletions(-) delete mode 100644 data/mydatabase.db create mode 100644 member_test.go diff --git a/api/api.go b/api/api.go index 95ff57a..0c4cf05 100644 --- a/api/api.go +++ b/api/api.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "gitea.tecamino.com/paadi/tecamino-logger/logging" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" ) @@ -14,7 +15,7 @@ type API struct { router *gin.Engine } -func NewAPI(host string, port int) *API { +func NewAPI(host string, port int, logger *logging.Logger) (*API, error) { r := gin.Default() r.Use(cors.New(cors.Config{ //AllowOrigins: []string{"http://localhost:9000"}, // frontend origin @@ -26,14 +27,17 @@ func NewAPI(host string, port int) *API { MaxAge: 12 * time.Hour, })) - apiHandler := NewAPIHandler() + apiHandler, err := NewAPIHandler(logger) + if err != nil { + return nil, err + } v1 := r.Group("v1") - v1.GET("/events", apiHandler.GetEventById) + v1.GET("/events", apiHandler.GetEvent) v1.GET("/events/new", apiHandler.StartNewEvent) v1.GET("/events/delete", apiHandler.DeleteEvent) - v1.GET("/members", apiHandler.GetMemberById) - v1.GET("/responsible", apiHandler.GetResponsibleById) + v1.GET("/members", apiHandler.GetMember) + v1.GET("/responsible", apiHandler.GetResponsible) v1.POST("/database/open", apiHandler.OpenDatabase) v1.POST("/members/add", apiHandler.AddNewMember) @@ -50,7 +54,7 @@ func NewAPI(host string, port int) *API { return &API{host: host, port: port, router: r, - } + }, nil } func (api *API) Run() error { diff --git a/api/apiHandler.go b/api/apiHandler.go index e991f3c..a7d55e9 100644 --- a/api/apiHandler.go +++ b/api/apiHandler.go @@ -1,20 +1,38 @@ package api import ( + "fmt" "net/http" + "os" + "path/filepath" "gitea.tecamino.com/paadi/memberDB/handlers" "gitea.tecamino.com/paadi/memberDB/models" + "gitea.tecamino.com/paadi/tecamino-logger/logging" "github.com/gin-gonic/gin" ) type APIHandler struct { DbHandler *handlers.DatabaseHandler + //dataHandler *dbHandler.DBHandler + logger *logging.Logger } -func NewAPIHandler() *APIHandler { - return &APIHandler{} +func NewAPIHandler(logger *logging.Logger) (aH *APIHandler, err error) { + if logger == nil { + logger, err = logging.NewLogger("memberDb.log", logging.DefaultConfig()) + if err != nil { + return nil, err + } + } + logger.Debug("NewAPIHandler", "initialize new api handler") + aH = &APIHandler{logger: logger} + return +} + +func (a *APIHandler) DBHandlerIsInitialized() bool { + return a.DbHandler != nil } func (a *APIHandler) OpenDatabase(c *gin.Context) { @@ -28,63 +46,54 @@ func (a *APIHandler) OpenDatabase(c *gin.Context) { } if database.Path == "" { + a.logger.Debug("OpenDatabase", "set default database path") database.SetDefaultPath() } if database.Token == "" { + a.logger.Debug("OpenDatabase", "set default token") database.SetDefaultToken() } - a.DbHandler, err = handlers.NewDatabaseHandler(database.Path, database.Create) + if _, err := os.Stat(database.Path); err != nil && !database.Create { + a.logger.Error("OpenDatabase", fmt.Sprintf("%s not found", database.Path)) + c.JSON(http.StatusBadRequest, gin.H{ + "message": fmt.Sprintf("%s not found", database.Path), + }) + return + } + + dbName := filepath.Base(database.Path) + folderpath := filepath.Dir(database.Path) + + a.DbHandler, err = handlers.NewDatabaseHandler(dbName, folderpath, a.logger) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + a.logger.Error("OpenDatabase", err) + c.JSON(http.StatusInternalServerError, nil) return } - if err := a.DbHandler.CreateNewMemberTable(); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + if err := a.DbHandler.AddNewTable(&models.Member{}); err != nil { + a.logger.Error("OpenDatabase", err) + c.JSON(http.StatusInternalServerError, nil) return } - if err := a.DbHandler.CreateNewEventTable(); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + if err := a.DbHandler.AddNewTable(&models.Event{}); err != nil { + a.logger.Error("OpenDatabase", err) + c.JSON(http.StatusInternalServerError, nil) return } - if err := a.DbHandler.CreateNewResponsibleTable(); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + if err := a.DbHandler.AddNewTable(&models.Person{}); err != nil { + a.logger.Error("OpenDatabase", err) + c.JSON(http.StatusInternalServerError, nil) return } a.DbHandler.SetToken(database.Token) - _, err = a.DbHandler.GetMember(0) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) - return - } c.JSON(http.StatusOK, gin.H{ "message": "database opened", }) } - -// databaseOpened is a helper function to first check wheter database is open for requests -func (a *APIHandler) databaseOpened(c *gin.Context) bool { - if a.DbHandler == nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": "no database opened", - }) - return false - } - return true -} diff --git a/api/eventHandler.go b/api/eventHandler.go index 1466722..437971a 100644 --- a/api/eventHandler.go +++ b/api/eventHandler.go @@ -9,22 +9,24 @@ import ( ) func (a *APIHandler) StartNewEvent(c *gin.Context) { - if !a.databaseOpened(c) { + if !a.DBHandlerIsInitialized() { + a.logger.Error("StartNewEvent", "database handler is not initialized") + c.JSON(http.StatusInternalServerError, nil) return } name := c.Query("name") if name == "" { - c.JSON(http.StatusBadRequest, gin.H{ - "message": "missing query 'name'", - }) + a.logger.Error("StartNewEvent", "missing query 'name'") + + c.JSON(http.StatusInternalServerError, nil) return } if err := a.DbHandler.StartNewEvent(name); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + a.logger.Error("StartNewEvent", err) + + c.JSON(http.StatusInternalServerError, nil) return } @@ -33,8 +35,10 @@ func (a *APIHandler) StartNewEvent(c *gin.Context) { }) } -func (a *APIHandler) GetEventById(c *gin.Context) { - if !a.databaseOpened(c) { +func (a *APIHandler) GetEvent(c *gin.Context) { + if !a.DBHandlerIsInitialized() { + a.logger.Error("GetEvent", "database handler is not initialized") + c.JSON(http.StatusInternalServerError, nil) return } @@ -45,18 +49,17 @@ func (a *APIHandler) GetEventById(c *gin.Context) { if id != "" { i, err = strconv.Atoi(id) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + a.logger.Error("GetEvent", err) + + c.JSON(http.StatusInternalServerError, nil) return } } - events, err := a.DbHandler.GetEvent(i) + events, err := a.DbHandler.GetEvent(uint(i)) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + a.logger.Error("GetEvent", err) + c.JSON(http.StatusBadRequest, nil) return } @@ -64,24 +67,27 @@ func (a *APIHandler) GetEventById(c *gin.Context) { } func (a *APIHandler) DeleteEvent(c *gin.Context) { - if !a.databaseOpened(c) { + if !a.DBHandlerIsInitialized() { + a.logger.Error("DeleteEvent", "database handler is not initialized") + c.JSON(http.StatusInternalServerError, nil) return } + var err error var request struct { - Ids []int `json:"ids"` + Ids []uint `json:"ids"` } err = c.BindJSON(&request) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + a.logger.Error("DeleteEvent", err) + c.JSON(http.StatusBadRequest, nil) return } err = a.DbHandler.DeleteEvent(request.Ids...) if err != nil { + a.logger.Error("DeleteEvent", err) c.JSON(http.StatusBadRequest, gin.H{ "message": err.Error(), }) @@ -93,22 +99,25 @@ func (a *APIHandler) DeleteEvent(c *gin.Context) { } func (a *APIHandler) AddNewAttendees(c *gin.Context) { - if !a.databaseOpened(c) { + if !a.DBHandlerIsInitialized() { + a.logger.Error("AddNewAttendees", "database handler is not initialized") + c.JSON(http.StatusInternalServerError, nil) return } var event models.Event err := c.BindJSON(&event) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + a.logger.Error("AddNewAttendees", err) + c.JSON(http.StatusInternalServerError, nil) return } err = a.DbHandler.AddAttendeesToEvent(event) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ + a.logger.Error("DeleteEvent", err) + + c.JSON(http.StatusInternalServerError, gin.H{ "message": err.Error(), }) return @@ -120,23 +129,26 @@ func (a *APIHandler) AddNewAttendees(c *gin.Context) { } func (a *APIHandler) DeleteAttendee(c *gin.Context) { - if !a.databaseOpened(c) { + if !a.DBHandlerIsInitialized() { + a.logger.Error("DeleteAttendee", "database handler is not initialized") + c.JSON(http.StatusInternalServerError, nil) return } + var err error var event models.Event err = c.BindJSON(&event) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + a.logger.Error("DeleteAttendee", err) + c.JSON(http.StatusInternalServerError, nil) return } err = a.DbHandler.DeleteAttendeesFromEvent(event) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ + a.logger.Error("DeleteAttendeesFromEvent", err) + c.JSON(http.StatusInternalServerError, gin.H{ "message": err.Error(), }) return diff --git a/api/membersHandler.go b/api/membersHandler.go index 4329ab1..6fe73da 100644 --- a/api/membersHandler.go +++ b/api/membersHandler.go @@ -16,16 +16,17 @@ import ( ) func (a *APIHandler) AddNewMember(c *gin.Context) { - if !a.databaseOpened(c) { + if !a.DBHandlerIsInitialized() { + a.logger.Error("AddNewMember", "database handler is not initialized") + c.JSON(http.StatusInternalServerError, nil) return } var member models.Member err := c.BindJSON(&member) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + a.logger.Error("AddNewMember", err) + c.JSON(http.StatusInternalServerError, nil) return } @@ -40,6 +41,7 @@ func (a *APIHandler) AddNewMember(c *gin.Context) { text += "birthday " } if text != "" { + a.logger.Error("AddNewMember", text+"can not be empty") c.JSON(http.StatusBadRequest, gin.H{ "message": text + "can not be empty", }) @@ -48,6 +50,7 @@ func (a *APIHandler) AddNewMember(c *gin.Context) { err = a.DbHandler.AddNewMember(member) if err != nil { + a.logger.Error("AddNewMember", err) c.JSON(http.StatusBadRequest, gin.H{ "message": err.Error(), }) @@ -59,8 +62,10 @@ func (a *APIHandler) AddNewMember(c *gin.Context) { }) } -func (a *APIHandler) GetMemberById(c *gin.Context) { - if !a.databaseOpened(c) { +func (a *APIHandler) GetMember(c *gin.Context) { + if !a.DBHandlerIsInitialized() { + a.logger.Error("GetMember", "database handler is not initialized") + c.JSON(http.StatusInternalServerError, nil) return } @@ -71,15 +76,15 @@ func (a *APIHandler) GetMemberById(c *gin.Context) { if id != "" { i, err = strconv.Atoi(id) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + a.logger.Error("GetMember", err) + c.JSON(http.StatusInternalServerError, nil) return } } - members, err := a.DbHandler.GetMember(i) + members, err := a.DbHandler.GetMember(uint(i)) if err != nil { + a.logger.Error("GetMember", err) c.JSON(http.StatusBadRequest, gin.H{ "message": err.Error(), }) @@ -89,7 +94,9 @@ func (a *APIHandler) GetMemberById(c *gin.Context) { } func (a *APIHandler) EditMember(c *gin.Context) { - if !a.databaseOpened(c) { + if !a.DBHandlerIsInitialized() { + a.logger.Error("EditMember", "database handler is not initialized") + c.JSON(http.StatusInternalServerError, nil) return } @@ -100,12 +107,12 @@ func (a *APIHandler) EditMember(c *gin.Context) { if id != "" { i, err = strconv.Atoi(id) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + a.logger.Error("EditMember", err) + c.JSON(http.StatusInternalServerError, nil) return } } else { + a.logger.Error("EditMember", err) c.JSON(http.StatusBadRequest, gin.H{ "message": "query parameter 'id' missing", }) @@ -115,14 +122,14 @@ func (a *APIHandler) EditMember(c *gin.Context) { var member models.Member err = c.BindJSON(&member) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + a.logger.Error("EditMember", err) + c.JSON(http.StatusInternalServerError, nil) return } err = a.DbHandler.UpdateMember(i, member) if err != nil { + a.logger.Error("EditMember", err) c.JSON(http.StatusBadRequest, gin.H{ "message": err.Error(), }) @@ -134,42 +141,49 @@ func (a *APIHandler) EditMember(c *gin.Context) { } func (a *APIHandler) DeleteMember(c *gin.Context) { - if !a.databaseOpened(c) { + if !a.DBHandlerIsInitialized() { + a.logger.Error("DeleteMember", "database handler is not initialized") + c.JSON(http.StatusInternalServerError, nil) return } + var err error var request struct { - Ids []int `json:"ids"` + Ids []uint `json:"ids"` } err = c.BindJSON(&request) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + a.logger.Error("DeleteMember", err) + c.JSON(http.StatusInternalServerError, nil) return } err = a.DbHandler.DeleteMember(request.Ids...) if err != nil { + a.logger.Error("DeleteMember", err) c.JSON(http.StatusBadRequest, gin.H{ "message": err.Error(), }) return } c.JSON(http.StatusOK, gin.H{ - "message": "member deleted", + "message": "member(s) deleted", }) } func (a *APIHandler) ImportCSV(c *gin.Context) { - if !a.databaseOpened(c) { + if !a.DBHandlerIsInitialized() { + a.logger.Error("ImportCSV", "database handler is not initialized") + c.JSON(http.StatusInternalServerError, nil) return } + var err error fileHeader, err := c.FormFile("file") if err != nil { + a.logger.Error("ImportCSV", err) c.JSON(http.StatusBadRequest, gin.H{ "message": err.Error(), }) @@ -188,7 +202,8 @@ func (a *APIHandler) ImportCSV(c *gin.Context) { rowIndexI, err := strconv.Atoi(rowIndex) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ + a.logger.Error("ImportCSV", err) + c.JSON(http.StatusInternalServerError, gin.H{ "message": err.Error(), }) return @@ -196,6 +211,7 @@ func (a *APIHandler) ImportCSV(c *gin.Context) { file, err := fileHeader.Open() if err != nil { + a.logger.Error("ImportCSV", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to open uploaded file"}) return } @@ -206,6 +222,7 @@ func (a *APIHandler) ImportCSV(c *gin.Context) { buf := make([]byte, sniffSize) n, err := file.Read(buf) if err != nil && err != io.EOF { + a.logger.Error("ImportCSV", err) c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) return } @@ -214,12 +231,14 @@ func (a *APIHandler) ImportCSV(c *gin.Context) { detector := chardet.NewTextDetector() result, err := detector.DetectBest(buf[:n]) if err != nil { + a.logger.Error("ImportCSV", err) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to detect encoding"}) return } _, err = file.Seek(0, io.SeekStart) if err != nil { + a.logger.Error("ImportCSV", err) c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to rewind file"}) return } @@ -234,6 +253,7 @@ func (a *APIHandler) ImportCSV(c *gin.Context) { case "ISO-8859-1": reader = transform.NewReader(file, charmap.ISO8859_1.NewDecoder()) default: + a.logger.Error("ImportCSV", "Unsupported encoding: "+result.Charset) c.JSON(http.StatusBadRequest, gin.H{"message": "Unsupported encoding: " + result.Charset}) return } @@ -243,6 +263,7 @@ func (a *APIHandler) ImportCSV(c *gin.Context) { records, err := csvReader.ReadAll() if err != nil { + a.logger.Error("ImportCSV", err) c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to parse CSV"}) return } @@ -298,6 +319,7 @@ func (a *APIHandler) ImportCSV(c *gin.Context) { } if message != "" { + a.logger.Error("ImportCSV", message) c.JSON(http.StatusBadRequest, gin.H{ "message": message, }) diff --git a/api/responsibleHandler.go b/api/responsibleHandler.go index 4997aff..0c8fcac 100644 --- a/api/responsibleHandler.go +++ b/api/responsibleHandler.go @@ -10,16 +10,17 @@ import ( ) func (a *APIHandler) AddNewResponsible(c *gin.Context) { - if !a.databaseOpened(c) { + if !a.DBHandlerIsInitialized() { + a.logger.Error("AddNewResponsible", "database handler is not initialized") + c.JSON(http.StatusInternalServerError, nil) return } var responsible models.Person err := c.BindJSON(&responsible) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + a.logger.Error("AddNewResponsible", err) + c.JSON(http.StatusInternalServerError, nil) return } @@ -36,11 +37,12 @@ func (a *APIHandler) AddNewResponsible(c *gin.Context) { }) } -func (a *APIHandler) GetResponsibleById(c *gin.Context) { - if !a.databaseOpened(c) { +func (a *APIHandler) GetResponsible(c *gin.Context) { + if !a.DBHandlerIsInitialized() { + a.logger.Error("GetResponsible", "database handler is not initialized") + c.JSON(http.StatusInternalServerError, nil) return } - var i int var err error @@ -48,15 +50,15 @@ func (a *APIHandler) GetResponsibleById(c *gin.Context) { if id != "" { i, err = strconv.Atoi(id) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + a.logger.Error("GetResponsible", err) + c.JSON(http.StatusInternalServerError, nil) return } } - members, err := a.DbHandler.GetResponsible(i) + members, err := a.DbHandler.GetResponsible(uint(i)) if err != nil { + a.logger.Error("GetResponsible", err) c.JSON(http.StatusBadRequest, gin.H{ "message": err.Error(), }) @@ -66,24 +68,26 @@ func (a *APIHandler) GetResponsibleById(c *gin.Context) { } func (a *APIHandler) DeleteResponsible(c *gin.Context) { - if !a.databaseOpened(c) { + if !a.DBHandlerIsInitialized() { + a.logger.Error("DeleteResponsible", "database handler is not initialized") + c.JSON(http.StatusInternalServerError, nil) return } var err error var request struct { - Ids []int `json:"ids"` + Ids []uint `json:"ids"` } err = c.BindJSON(&request) if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) + a.logger.Error("DeleteResponsible", err) + c.JSON(http.StatusBadRequest, nil) return } err = a.DbHandler.DeleteResponsible(request.Ids...) if err != nil { + a.logger.Error("DeleteResponsible", err) c.JSON(http.StatusBadRequest, gin.H{ "message": err.Error(), }) diff --git a/data/mydatabase.db b/data/mydatabase.db deleted file mode 100644 index 6859bab700b379830b088c19af1d19068b18cb7d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeI*OK;jp8~|`=(xizRQE9cxA*w`gt!NiN#xJdq)(}H@gaEM#mS|05fZJ^%4|ejXxC;64?mXC*gXN9)+3Bo1JhQeJ{T*yAk>B*KhV>e@C{T%8|X_Vt>Yd zk2!nFQ+e;%x+-uS1V8`;KmY_l00ck)1VG@66y3r+i5D*vZuK8YW zJ5y6+Rf(%|x}?N!BjPW2cXn(X&zDstr_|z=TK-tBHRDC48J9KnBwr3I9xG+_br|RB zJrY-yh8i!QgsWC6T^+@GIC)tf)yny*rhHI%-4Y@G-Fjw!g1*f^sXX4=Pb8wvrNF9^PrMNc89v(G`oQ9&!k1T_ zd>z(&^;0#vy?<~J{bja1XOHv@GW1uQM<1MHb?{$dTK3QnF77A2ExTyze*3OnS6^HA zUD2AVhCK}0SfAZJ=}pO+iaN$a;@@*4=l0;j?BjceM$n(QciS1X`|dr#q^H~6>*#w1 z(lyE0_U&M8%(Cn2L&Bl;W^-#_K3F%jR=4qjU1c7>+4z)jW);5q=Fz8w)2r}zbNjvU z2TL^kxY|Mj0T2KI5C8!X009sH0T2Lzhb-{(W%S!fZ?P9Hqs89hF(H?ii|32yvq9I> zPEM*7mhiQsj&~@f3p3Ym7K(F8GSEn&squn;m(*fh_H9EAj$p$W$^#JuX#Rl|Zo3?%WA#Y&>d2#D3KikhLE zq|*-shZ39mI6dXh)68&MM2YOFj|Xix*`nLVVQpq=obH_E9dFR?8d;PHG>sW{*-D#x)k;YY^_wNCI`c4@b+91S59_TIuI1fPMWHHvoTLwtbFL=jY^`?e1tY8I zWyj1ZrPgblpsM+lqU0wdJ29{HI>)V<)^R0su9m3bNHbb3zL91)y;WIb!F_;`=WW4C?pFloOOl#Vbf@cM_nmXXid@?Pd3b~ zb3Q3qd~xigD(0D%v)k=V&+YStMxSPrY`2@w`lVctrD_9BsdwwD#W$?Z>2Z(O@mvpP z>Y&;lsJ*&d@1Bq z`UVj67z9871V8`;KmY_l00ck)1VG?_A+VhPf3D~Mmw*2s`zsPIkU#(gKmY_l00ck) z1V8`;KmY_l00bV8z;1NoU}yCrz~y`Y;9dL== 0 { - query += ` WHERE id = ?` - args = id + if !dh.DatabaseOpened() { + return errors.New("database not opened") } - rows, err := dh.database.Query(query, args) + return dh.database.AddNewColum(&models.Event{ + Name: name, + Date: time.Now().Format("2006-01-02 15:04:05"), + }) +} + +func (dh *DatabaseHandler) GetEvent(id uint) (attendees []models.Event, err error) { + if !dh.DatabaseOpened() { + return attendees, errors.New("database not opened") + } + + err = dh.database.GetById(&attendees, id) if err != nil { return } - defer rows.Close() - for rows.Next() { - var id, count int - var name, date, attendance string - if err = rows.Scan(&id, &name, &date, &attendance, &count); err != nil { - return - } - - var a []models.Person - - if err := json.Unmarshal([]byte(attendance), &a); err != nil { - return attendees, err - } - - attendees = append(attendees, models.Event{ - Id: id, - Name: name, - Date: date, - Attendees: a, - Count: count, - }) + for i := range attendees { + attendees[i].Count = len(attendees[i].Attendees) } - - return attendees, nil + return } -func (dh *DatabaseHandler) DeleteEvent(ids ...int) error { +func (dh *DatabaseHandler) DeleteEvent(ids ...uint) error { + if !dh.DatabaseOpened() { + return errors.New("database not opened") + } + if len(ids) == 0 { return errors.New("no ids given to be deleted") } - placeholders := make([]string, len(ids)) - args := make([]any, len(ids)) - for i, id := range ids { - placeholders[i] = "?" - args[i] = id - } - query := fmt.Sprintf("DELETE FROM events WHERE id IN (%s)", strings.Join(placeholders, ",")) - - _, err := dh.database.Exec(query, args...) - return err + return dh.database.DeleteById(&models.Event{}, ids...) } -func (dh *DatabaseHandler) AddAttendeesToEvent(event models.Event) error { - - //get event from database by id - row := dh.database.QueryRow(`SELECT attendees FROM events WHERE id = ?`, event.Id) - - var attendeesString string - if err := row.Scan(&attendeesString); err != nil { - return err +func (dh *DatabaseHandler) AddAttendeesToEvent(newEvent models.Event) error { + if !dh.DatabaseOpened() { + return errors.New("database not opened") } - fmt.Println(attendeesString) - var attendees []models.Person - err := json.Unmarshal([]byte(attendeesString), &attendees) + var event models.Event + err := dh.database.GetById(&event, uint(event.Id)) if err != nil { return err } next: - for _, newAttendee := range event.Attendees { - for _, attendee := range attendees { + for _, newAttendee := range newEvent.Attendees { + for _, attendee := range event.Attendees { if attendee.FirstName == newAttendee.FirstName && attendee.LastName == newAttendee.LastName { continue next } } - attendees = append(attendees, newAttendee) + event.Attendees = append(event.Attendees, newAttendee) } - attendeesByte, err := json.Marshal(attendees) - if err != nil { - return err - } + event.Count = len(event.Attendees) - count := len(attendees) - _, err = dh.database.Exec("UPDATE events SET attendees= ? count= ? WHERE id= ?", string(attendeesByte), event.Id, count) - if err != nil { - return err - } - - return nil + return dh.database.UpdateValuesById(&event, uint(event.Id)) } -func (dh *DatabaseHandler) DeleteAttendeesFromEvent(event models.Event) error { - //get event from database by id - row := dh.database.QueryRow(`SELECT attendees FROM events WHERE id = ?`, event.Id) - - var attendeesString string - if err := row.Scan(&attendeesString); err != nil { - return err - } - fmt.Println(attendeesString) - - var attendees []models.Person - err := json.Unmarshal([]byte(attendeesString), &attendees) - if err != nil { - return err +func (dh *DatabaseHandler) DeleteAttendeesFromEvent(newEvent models.Event) error { + if !dh.DatabaseOpened() { + return errors.New("database not opened") } - for _, newAttendee := range event.Attendees { - for i, attendee := range attendees { - if attendee.FirstName == newAttendee.FirstName && attendee.LastName == newAttendee.LastName { - attendees = slices.Delete(attendees, i, i+1) + var event models.Event + dh.database.GetById(&event, uint(event.Id)) + +next: + for _, newAttendee := range newEvent.Attendees { + for i := range event.Attendees { + if event.Attendees[i].FirstName == newAttendee.FirstName && event.Attendees[i].LastName == newAttendee.LastName { + event.Attendees = slices.Delete(event.Attendees, i, i+1) + continue next } } } - - attendeesByte, err := json.Marshal(attendees) - if err != nil { - return err - } - - count := len(attendees) - _, err = dh.database.Exec("UPDATE events SET attendees= ? count= ? WHERE id= ?", string(attendeesByte), event.Id, count) - if err != nil { - return err - } - - return nil + event.Count = len(event.Attendees) + return dh.database.UpdateValuesById(&event, uint(event.Id)) } diff --git a/handlers/members.go b/handlers/members.go index 2be82e2..78cda7f 100644 --- a/handlers/members.go +++ b/handlers/members.go @@ -1,10 +1,8 @@ package handlers import ( - "database/sql" "errors" "fmt" - "strings" "time" "gitea.tecamino.com/paadi/memberDB/crypto" @@ -12,37 +10,12 @@ import ( "gitea.tecamino.com/paadi/memberDB/utils" ) -// CreateNewMemberTable creates a new member table. -func (dh *DatabaseHandler) CreateNewMemberTable() error { - createTableSQL := ` - CREATE TABLE IF NOT EXISTS members ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - first_name TEXT NOT NULL, - first_name_hash TEXT NOT NULL, - last_name TEXT NOT NULL, - last_name_hash TEXT NOT NULL, - birthday TEXT NOT NULL, - birthday_hash TEXT NOT NULL, - address TEXT NOT NULL, - zip_code TEXT NOT NULL, - town TEXT NOT NULL, - phone TEXT NOT NULL, - email TEXT UNIQUE NOT NULL, - first_visit TEXT NOT NULL, - last_visit TEXT NOT NULL, - member_group TEXT NOT NULL, - responsible_person TEXT NOT NULL - );` - - _, err := dh.database.Exec(createTableSQL) - if err != nil { - return fmt.Errorf("failed to create table: %w", err) - } - return nil -} - // AddNewMember adds a new member to memeber table at least fist, las name and birthday has to be entered func (dh *DatabaseHandler) AddNewMember(members ...models.Member) error { + if !dh.DatabaseOpened() { + return errors.New("database not opened") + } + for _, member := range members { exists, err := dh.memberExists(member) @@ -123,7 +96,23 @@ func (dh *DatabaseHandler) AddNewMember(members ...models.Member) error { return err } - _, err = dh.database.Exec("INSERT INTO members (first_name, first_name_hash, last_name, last_name_hash, birthday, birthday_hash, address, zip_code, town, phone, email, first_visit, last_visit, member_group, responsible_person) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", encFirstName, dh.hashField(member.FirstName), encLastName, dh.hashField(member.LastName), encBirthday, dh.hashField(member.Birthday), encAddress, encZip, encTown, encPhone, encEmail, encFirstVisit, encLastVisit, encGroup, encResponsiblePerson) + member.FirstNameHash = dh.hashField(member.FirstName) + member.FirstName = encFirstName + member.LastNameHash = dh.hashField(member.LastName) + member.LastName = encLastName + member.BirthdayHash = dh.hashField(member.Birthday) + member.Birthday = encBirthday + member.Address = encAddress + member.Zip = encZip + member.Town = encTown + member.Phone = encPhone + member.Email = encEmail + member.FirstVisit = encFirstVisit + member.LastVisit = encLastVisit + member.Group = encGroup + member.ResponsiblePerson = encResponsiblePerson + + err = dh.database.AddNewColum(&member) if err != nil { return err } @@ -132,290 +121,203 @@ func (dh *DatabaseHandler) AddNewMember(members ...models.Member) error { } // DeleteMember removes members by given ids -func (dh *DatabaseHandler) DeleteMember(ids ...int) error { +func (dh *DatabaseHandler) DeleteMember(ids ...uint) error { + if !dh.DatabaseOpened() { + return errors.New("database not opened") + } + if len(ids) == 0 { return errors.New("no ids given to be deleted") } - - placeholders := make([]string, len(ids)) - args := make([]any, len(ids)) - for i, id := range ids { - placeholders[i] = "?" - args[i] = id - } - query := fmt.Sprintf("DELETE FROM members WHERE id IN (%s)", strings.Join(placeholders, ",")) - - _, err := dh.database.Exec(query, args...) - return err + return dh.database.DeleteById(&models.Member{}, ids...) } // GetMember returns one member by given id -func (dh *DatabaseHandler) GetMember(id int) (members []models.Member, err error) { - var args any - query := `SELECT id, first_name, last_name, birthday, address, zip_code, town, phone, email, first_visit, last_visit, member_group, responsible_person FROM members` - - if id > 0 { - query = ` WHERE id = ?` - args = id +func (dh *DatabaseHandler) GetMember(id uint) (members []models.Member, err error) { + if !dh.DatabaseOpened() { + return members, errors.New("database not opened") } - rows, err := dh.database.Query(query, args) + err = dh.database.GetById(&members, id) if err != nil { return } - defer rows.Close() - for rows.Next() { - var id int - var encFirstName, encLastName, encBirthday, encAddress, encZip, encTown, encPhone, encEmail, encFirstVisit, encLastVisit, encGroup, encResponsiblePerson string - if err = rows.Scan(&id, &encFirstName, &encLastName, &encBirthday, &encAddress, &encZip, &encTown, &encPhone, &encEmail, &encFirstVisit, &encLastVisit, &encGroup, &encResponsiblePerson); err != nil { + for i := range members { + members[i].FirstName, err = crypto.Decrypt(members[i].FirstName, dh.token) + if err != nil { return } - firstName, err := crypto.Decrypt(encFirstName, dh.token) + members[i].LastName, err = crypto.Decrypt(members[i].LastName, dh.token) if err != nil { - return members, err + return } - lastName, err := crypto.Decrypt(encLastName, dh.token) + members[i].Birthday, err = crypto.Decrypt(members[i].Birthday, dh.token) if err != nil { - return members, err + return } - birthday, err := crypto.Decrypt(encBirthday, dh.token) + members[i].Address, err = crypto.Decrypt(members[i].Address, dh.token) if err != nil { - return members, err + return } - address, err := crypto.Decrypt(encAddress, dh.token) + members[i].Zip, err = crypto.Decrypt(members[i].Zip, dh.token) if err != nil { - return members, err + return } - zip, err := crypto.Decrypt(encZip, dh.token) + members[i].Town, err = crypto.Decrypt(members[i].Town, dh.token) if err != nil { - return members, err + return } - town, err := crypto.Decrypt(encTown, dh.token) + members[i].Phone, err = crypto.Decrypt(members[i].Phone, dh.token) if err != nil { - return members, err + return } - phone, err := crypto.Decrypt(encPhone, dh.token) + members[i].Email, err = crypto.Decrypt(members[i].Email, dh.token) if err != nil { - return members, err + return } - email, err := crypto.Decrypt(encEmail, dh.token) + members[i].FirstVisit, err = crypto.Decrypt(members[i].FirstVisit, dh.token) if err != nil { - return members, err + return } - firstVisit, err := crypto.Decrypt(encFirstVisit, dh.token) + members[i].LastVisit, err = crypto.Decrypt(members[i].LastVisit, dh.token) if err != nil { - return members, err + return } - lastVisit, err := crypto.Decrypt(encLastVisit, dh.token) + members[i].Group, err = crypto.Decrypt(members[i].Group, dh.token) if err != nil { - return members, err + return } - group, err := crypto.Decrypt(encGroup, dh.token) + members[i].ResponsiblePerson, err = crypto.Decrypt(members[i].ResponsiblePerson, dh.token) if err != nil { - return members, err + return } - - responsiblePerson, err := crypto.Decrypt(encResponsiblePerson, dh.token) - if err != nil { - return members, err - } - - members = append(members, models.Member{ - Id: id, - FirstName: firstName, - LastName: lastName, - Birthday: birthday, - Address: address, - Zip: zip, - Town: town, - Phone: phone, - Email: email, - FirstVisit: firstVisit, - LastVisit: lastVisit, - Group: group, - ResponsiblePerson: responsiblePerson, - }) } - - return members, nil + return } // UpdateMember updates/overrides all information given meber id func (dh *DatabaseHandler) UpdateMember(id int, member models.Member) (err error) { - var queryParameters []string - var args []any + if !dh.DatabaseOpened() { + return errors.New("database not opened") + } if member.FirstName != "" { - encFirstName, err := crypto.Encrypt(member.FirstName, dh.token) + member.FirstNameHash = dh.hashField(member.FirstName) + member.FirstName, err = crypto.Encrypt(member.FirstName, dh.token) if err != nil { - return err + return } - - queryParameters = append(queryParameters, "first_name = ?") - queryParameters = append(queryParameters, "first_name_hash = ?") - - args = append(args, encFirstName) - args = append(args, dh.hashField(member.FirstName)) } if member.LastName != "" { - encLastName, err := crypto.Encrypt(member.LastName, dh.token) + member.LastNameHash = dh.hashField(member.LastName) + member.LastName, err = crypto.Encrypt(member.LastName, dh.token) if err != nil { - return err + return } - - queryParameters = append(queryParameters, "last_name = ?") - queryParameters = append(queryParameters, "last_name_hash = ?") - - args = append(args, encLastName) - args = append(args, dh.hashField(member.LastName)) } //check correct birtday format if member.Birthday != "" && utils.IsValidBirthday(member.Birthday) { - encBirthday, err := crypto.Encrypt(member.Birthday, dh.token) + member.BirthdayHash = dh.hashField(member.BirthdayHash) + member.Birthday, err = crypto.Encrypt(member.Birthday, dh.token) if err != nil { return err } - - queryParameters = append(queryParameters, "birthday = ?") - queryParameters = append(queryParameters, "birthday_hash = ?") - - args = append(args, encBirthday) - args = append(args, dh.hashField(member.Birthday)) } else if member.Birthday != "" { return errors.New("incorrect birthday format") } if member.Address != "" { - encAddress, err := crypto.Encrypt(member.Address, dh.token) + member.Address, err = crypto.Encrypt(member.Address, dh.token) if err != nil { return err } - queryParameters = append(queryParameters, "address = ?") - - args = append(args, encAddress) } if member.Zip != "" { - encZip, err := crypto.Encrypt(member.Zip, dh.token) + member.Zip, err = crypto.Encrypt(member.Zip, dh.token) if err != nil { return err } - queryParameters = append(queryParameters, "zip_code = ?") - - args = append(args, encZip) } if member.Town != "" { - encTown, err := crypto.Encrypt(member.Town, dh.token) + member.Town, err = crypto.Encrypt(member.Town, dh.token) if err != nil { return err } - queryParameters = append(queryParameters, "town = ?") - - args = append(args, encTown) } if member.Phone != "" { - encPhone, err := crypto.Encrypt(member.Phone, dh.token) + member.Phone, err = crypto.Encrypt(member.Phone, dh.token) if err != nil { return err } - queryParameters = append(queryParameters, "phone = ?") - - args = append(args, encPhone) } //check correct email format if member.Email != "" && utils.IsValidEmail(member.Email) { - encEmail, err := crypto.Encrypt(member.Email, dh.token) + member.Email, err = crypto.Encrypt(member.Email, dh.token) if err != nil { return err } - queryParameters = append(queryParameters, "email = ?") - - args = append(args, encEmail) } else if member.Email != "" { return errors.New("incorrect email format") } if member.FirstVisit != "" { - encFirstVisit, err := crypto.Encrypt(member.FirstVisit, dh.token) + member.FirstVisit, err = crypto.Encrypt(member.FirstVisit, dh.token) if err != nil { return err } - queryParameters = append(queryParameters, "first_visit = ?") - - args = append(args, encFirstVisit) } if member.LastVisit != "" { - encLastVisit, err := crypto.Encrypt(member.LastVisit, dh.token) + member.LastVisit, err = crypto.Encrypt(member.LastVisit, dh.token) if err != nil { return err } - queryParameters = append(queryParameters, "last_visit = ?") - - args = append(args, encLastVisit) } if member.Group != "" { - encFirstVisit, err := crypto.Encrypt(member.Group, dh.token) + member.Group, err = crypto.Encrypt(member.Group, dh.token) if err != nil { return err } - queryParameters = append(queryParameters, "member_group = ?") - - args = append(args, encFirstVisit) } if member.ResponsiblePerson != "" { - encResponsiblePerson, err := crypto.Encrypt(member.ResponsiblePerson, dh.token) + member.ResponsiblePerson, err = crypto.Encrypt(member.ResponsiblePerson, dh.token) if err != nil { return err } - queryParameters = append(queryParameters, "responsible_person = ?") - - args = append(args, encResponsiblePerson) } - - query := `UPDATE members SET ` - query += strings.Join(queryParameters, ", ") - query += ` WHERE id = ?` - args = append(args, id) - - _, err = dh.database.Exec(query, args...) - return err + return dh.database.UpdateValuesById(&member, uint(member.Id)) } // memberExists helper to check wheter member already exists -func (dh *DatabaseHandler) memberExists(member models.Member) (bool, error) { - query := ` - SELECT 1 FROM members - WHERE first_name_hash = ? AND last_name_hash = ? AND birthday_hash = ? - LIMIT 1 - ` - var exists int - err := dh.database.QueryRow(query, dh.hashField(member.FirstName), dh.hashField(member.LastName), dh.hashField(member.Birthday)).Scan(&exists) - - if err == sql.ErrNoRows { - return false, nil // no match - } else if err != nil { - return false, err // db error +func (dh *DatabaseHandler) memberExists(checkMember models.Member) (bool, error) { + if !dh.DatabaseOpened() { + return false, errors.New("database not opened") } - return true, nil // match found + var member models.Member + err := dh.database.Exists(&member, "birthdayHash", dh.hashField(checkMember.Birthday), false) + if err != nil { + return false, nil + } + return dh.hashField(checkMember.FirstName) == member.FirstNameHash && dh.hashField(checkMember.LastName) == member.LastNameHash && dh.hashField(checkMember.Birthday) == member.BirthdayHash, nil } diff --git a/handlers/responsible.go b/handlers/responsible.go index fb5cffb..4c54625 100644 --- a/handlers/responsible.go +++ b/handlers/responsible.go @@ -1,30 +1,18 @@ package handlers import ( - "database/sql" "errors" - "fmt" - "strings" "gitea.tecamino.com/paadi/memberDB/models" ) -func (dh *DatabaseHandler) CreateNewResponsibleTable() error { - createTableSQL := ` - CREATE TABLE IF NOT EXISTS responsible ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - first_name TEXT NOT NULL, - last_name TEXT UNIQUE NOT NULL - );` - - _, err := dh.database.Exec(createTableSQL) - if err != nil { - return fmt.Errorf("failed to create table: %w", err) - } - return nil -} - func (dh *DatabaseHandler) AddNewResponsible(responsibles ...models.Person) error { + if !dh.DatabaseOpened() { + return errors.New("database not opened") + } + if !dh.DatabaseOpened() { + return errors.New("database not opened") + } for _, responsible := range responsibles { exists, err := dh.responsibleExists(responsible) @@ -34,7 +22,7 @@ func (dh *DatabaseHandler) AddNewResponsible(responsibles ...models.Person) erro continue } - _, err = dh.database.Exec("INSERT INTO responsible (first_name, last_name) VALUES (?, ?)", responsible.FirstName, responsible.LastName) + err = dh.database.AddNewColum(&responsible) if err != nil { return err } @@ -42,69 +30,35 @@ func (dh *DatabaseHandler) AddNewResponsible(responsibles ...models.Person) erro return nil } -func (dh *DatabaseHandler) GetResponsible(id int) (persons []models.Person, err error) { - var args any - query := `SELECT id, first_name, last_name FROM responsible` - - if id > 0 { - query = ` WHERE id = ?` - args = id +func (dh *DatabaseHandler) GetResponsible(id uint) (persons []models.Person, err error) { + if !dh.DatabaseOpened() { + return persons, errors.New("database not opened") } - rows, err := dh.database.Query(query, args) - if err != nil { - return - } - defer rows.Close() - - for rows.Next() { - var id int - var firstName, lastName string - if err = rows.Scan(&id, &firstName, &lastName); err != nil { - return - } - - persons = append(persons, models.Person{ - Id: id, - FirstName: firstName, - LastName: lastName, - }) - } - - return persons, nil + err = dh.database.GetById(&persons, id) + return } -func (dh *DatabaseHandler) DeleteResponsible(ids ...int) error { +func (dh *DatabaseHandler) DeleteResponsible(ids ...uint) error { + if !dh.DatabaseOpened() { + return errors.New("database not opened") + } + if len(ids) == 0 { return errors.New("no ids given to be deleted") } - - placeholders := make([]string, len(ids)) - args := make([]any, len(ids)) - for i, id := range ids { - placeholders[i] = "?" - args[i] = id - } - query := fmt.Sprintf("DELETE FROM responsible WHERE id IN (%s)", strings.Join(placeholders, ",")) - - _, err := dh.database.Exec(query, args...) - return err + return dh.database.DeleteById(&models.Person{}, ids...) } -func (dh *DatabaseHandler) responsibleExists(responsible models.Person) (bool, error) { - query := ` - SELECT 1 FROM responsible - WHERE first_name = ? AND last_name = ? - LIMIT 1 - ` - var exists int - err := dh.database.QueryRow(query, responsible.FirstName, responsible.LastName).Scan(&exists) - - if err == sql.ErrNoRows { - return false, nil // no match - } else if err != nil { - return false, err // db error +func (dh *DatabaseHandler) responsibleExists(CheckResponsible models.Person) (bool, error) { + if !dh.DatabaseOpened() { + return false, errors.New("database not opened") } - return true, nil // match found + var person models.Person + err := dh.database.Exists(&person, "lastName", CheckResponsible.LastName, false) + if err != nil { + return false, nil + } + return person.FirstName == CheckResponsible.FirstName && person.LastName == CheckResponsible.LastName, nil } diff --git a/main.go b/main.go index 654ef3c..7cd8491 100644 --- a/main.go +++ b/main.go @@ -12,8 +12,13 @@ func main() { url := flag.String("url", "127.0.0.1", "server url") flag.Parse() - a := api.NewAPI(*url, *port) - if err := a.Run(); err != nil { + a, err := api.NewAPI(*url, *port, nil) + if err != nil { + log.Fatal(err) + } + + err = a.Run() + if err != nil { log.Fatal(err) } } diff --git a/member_test.go b/member_test.go new file mode 100644 index 0000000..a0e212e --- /dev/null +++ b/member_test.go @@ -0,0 +1,219 @@ +package main + +import ( + "bytes" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "os" + "testing" + + "gitea.tecamino.com/paadi/memberDB/api" + "gitea.tecamino.com/paadi/memberDB/models" + "github.com/gin-gonic/gin" + "github.com/go-playground/assert/v2" +) + +func TestErrors(t *testing.T) { + dbName := "test.db" + if _, err := os.Stat(dbName); err == nil { + t.Log("remove user.db to start test with empty database") + if err := os.Remove(dbName); err != nil { + t.Fatal(err) + } + } + + t.Log("start member db test") + + t.Log("initialize accessHandler") + + r := gin.Default() + + apiHandler, err := api.NewAPIHandler(nil) + if err != nil { + t.Fatal(err) + } + + v1 := r.Group("v1") + v1.GET("/events", apiHandler.GetEvent) + v1.GET("/events/new", apiHandler.StartNewEvent) + v1.GET("/events/delete", apiHandler.DeleteEvent) + v1.GET("/members", apiHandler.GetMember) + v1.GET("/responsible", apiHandler.GetResponsible) + + v1.POST("/database/open", apiHandler.OpenDatabase) + v1.POST("/members/add", apiHandler.AddNewMember) + v1.POST("/members/edit", apiHandler.EditMember) + v1.POST("/members/delete", apiHandler.DeleteMember) + v1.POST("/members/import/csv", apiHandler.ImportCSV) + + v1.POST("/events/attendees/add", apiHandler.AddNewAttendees) + v1.POST("/events/attendees/delete", apiHandler.DeleteAttendee) + + v1.POST("/responsible/add", apiHandler.AddNewResponsible) + v1.POST("/responsible/delete", apiHandler.DeleteResponsible) + + type request struct { + Log string + Name string + Method string + Path string + Payload any + Cookie *http.Cookie + ignoreError bool + } + var requests []request + + type payload struct { + DBPath string `json:"dbPath,omitempty"` + Create bool `json:"create,omitempty"` + FirstName string `json:"FirstName,omitempty"` + LastName string `json:"lastName,omitempty"` + Birthday string `json:"birthday,omitempty"` + } + requests = append(requests, + + request{Log: "error db not opened", Name: "error db not opened", Method: "POST", Path: "/v1/members/add", Payload: payload{FirstName: "Adrian", LastName: "Zürcher", Birthday: "23.06.1987"}, ignoreError: true}, + request{Log: "open member db", Name: "open member db", Method: "POST", Path: "/v1/database/open", Payload: payload{DBPath: "test.db", Create: true}}, + request{Log: "add new member", Name: "add new member", Method: "POST", Path: "/v1/members/add", Payload: payload{FirstName: "Adrian", LastName: "Zürcher", Birthday: "23.06.1987"}, ignoreError: true}, + request{Log: "error first name missing", Name: "error first name missing", Method: "POST", Path: "/v1/members/add", Payload: payload{LastName: "Zürcher", Birthday: "23.06.1987"}, ignoreError: true}, + request{Log: "error last name missing", Name: "error last name missing", Method: "POST", Path: "/v1/members/add", Payload: payload{FirstName: "Adrian", Birthday: "23.06.1987"}, ignoreError: true}, + request{Log: "error birthday missing", Name: "error birthday missing", Method: "POST", Path: "/v1/members/add", Payload: payload{FirstName: "Paulina", LastName: "Zürcher"}, ignoreError: true}, + request{Log: "error wrong birthday format", Name: "error wrong birthday format", Method: "POST", Path: "/v1/members/add", Payload: payload{FirstName: "Paulina", LastName: "Zürcher", Birthday: "213.06.1987"}, ignoreError: true}, + ) + + for _, request := range requests { + if request.Log != "" { + t.Log(request.Log) + + } + var bodyReader io.Reader + if request.Payload != nil { + jsonBytes, _ := json.Marshal(request.Payload) + bodyReader = bytes.NewBuffer(jsonBytes) + } + req, _ := http.NewRequest(request.Method, request.Path, bodyReader) + if request.Cookie != nil { + req.AddCookie(request.Cookie) // attach refresh_token cookie + } + w := httptest.NewRecorder() + + r.ServeHTTP(w, req) + + t.Log(request.Name+" response:", w.Body.String()) + if !request.ignoreError { + assert.Equal(t, http.StatusOK, w.Code) + } + } +} +func TestMemberDB(t *testing.T) { + dbName := "test.db" + if _, err := os.Stat(dbName); err == nil { + t.Log("remove user.db to start test with empty database") + if err := os.Remove(dbName); err != nil { + t.Fatal(err) + } + } + + t.Log("start member db test") + + t.Log("initialize accessHandler") + + r := gin.Default() + + apiHandler, err := api.NewAPIHandler(nil) + if err != nil { + t.Fatal(err) + } + + v1 := r.Group("v1") + v1.GET("/events", apiHandler.GetEvent) + v1.GET("/events/new", apiHandler.StartNewEvent) + v1.GET("/events/delete", apiHandler.DeleteEvent) + v1.GET("/members", apiHandler.GetMember) + v1.GET("/responsible", apiHandler.GetResponsible) + + v1.POST("/database/open", apiHandler.OpenDatabase) + v1.POST("/members/add", apiHandler.AddNewMember) + v1.POST("/members/edit", apiHandler.EditMember) + v1.POST("/members/delete", apiHandler.DeleteMember) + v1.POST("/members/import/csv", apiHandler.ImportCSV) + + v1.POST("/events/attendees/add", apiHandler.AddNewAttendees) + v1.POST("/events/attendees/delete", apiHandler.DeleteAttendee) + + v1.POST("/responsible/add", apiHandler.AddNewResponsible) + v1.POST("/responsible/delete", apiHandler.DeleteResponsible) + + type request struct { + Log string + Name string + Method string + Path string + Payload any + Cookie *http.Cookie + ignoreError bool + } + var requests []request + + type payload struct { + DBPath string `json:"dbPath,omitempty"` + Create bool `json:"create,omitempty"` + Ids []uint `json:"ids,omitempty"` + FirstName string `json:"FirstName,omitempty"` + LastName string `json:"lastName,omitempty"` + Birthday string `json:"birthday,omitempty"` + Group string `json:"group,omitempty"` + } + + requests = append(requests, + request{Log: "open member db", Name: "open member db", Method: "POST", Path: "/v1/database/open", Payload: payload{DBPath: "test.db", Create: true}}, + + request{Log: "add new member", Name: "add new member", Method: "POST", Path: "/v1/members/add", Payload: payload{FirstName: "Adrian", LastName: "Zürcher", Birthday: "23.06.1987"}}, + request{Log: "add existing member", Name: "add existing member", Method: "POST", Path: "/v1/members/add", Payload: payload{FirstName: "Adrian", LastName: "Zürcher", Birthday: "23.06.1987"}, ignoreError: true}, + request{Log: "add new member", Name: "add new member", Method: "POST", Path: "/v1/members/add", Payload: payload{FirstName: "Paulina", LastName: "Zürcher", Birthday: "15.01.1991"}}, + request{Log: "get members", Name: "get members", Method: "GET", Path: "/v1/members"}, + request{Log: "update members", Name: "update members", Method: "POST", Path: "/v1/members/edit?id=1", Payload: payload{Group: "testGroup"}}, + request{Log: "get again members", Name: "get again members", Method: "GET", Path: "/v1/members"}, + request{Log: "delete members", Name: "delete members", Method: "POST", Path: "/v1/members/delete", Payload: payload{Ids: []uint{2, 1}}}, + request{Log: "get again members", Name: "get again members", Method: "GET", Path: "/v1/members"}, + + request{Log: "new event", Name: "new event", Method: "GET", Path: "/v1/events/new?name=testEvent"}, + request{Log: "add new attendee", Name: "add new attendee", Method: "POST", Path: "/v1/events/attendees/add", Payload: models.Event{Attendees: models.Persons{{FirstName: "Adi", LastName: "Züri"}}}}, + request{Log: "add another attendee", Name: "add another attendee", Method: "POST", Path: "/v1/events/attendees/add", Payload: models.Event{Attendees: models.Persons{{FirstName: "Pau", LastName: "Züri"}}}}, + request{Log: "get events", Name: "get events", Method: "GET", Path: "/v1/events"}, + request{Log: "add delete attendee", Name: "add delete attendee", Method: "POST", Path: "/v1/events/attendees/delete", Payload: models.Event{Attendees: models.Persons{{FirstName: "Adi", LastName: "Züri"}}}}, + request{Log: "get events", Name: "get events", Method: "GET", Path: "/v1/events"}, + + request{Log: "add responsible", Name: "add responsible", Method: "POST", Path: "/v1/responsible/add", Payload: models.Person{FirstName: "Max", LastName: "Muster"}}, + request{Log: "add another responsible", Name: "add another responsible", Method: "POST", Path: "/v1/responsible/add", Payload: models.Person{FirstName: "Hausi", LastName: "Muster"}}, + request{Log: "get responsible", Name: "get responsible", Method: "GET", Path: "/v1/responsible"}, + request{Log: "delete responsible id 1", Name: "delete responsible id 1", Method: "POST", Path: "/v1/responsible/delete", Payload: payload{Ids: []uint{1}}}, + request{Log: "get responsible", Name: "get responsible", Method: "GET", Path: "/v1/responsible"}, + ) + + for _, request := range requests { + if request.Log != "" { + t.Log(request.Log) + + } + var bodyReader io.Reader + if request.Payload != nil { + jsonBytes, _ := json.Marshal(request.Payload) + bodyReader = bytes.NewBuffer(jsonBytes) + } + req, _ := http.NewRequest(request.Method, request.Path, bodyReader) + if request.Cookie != nil { + req.AddCookie(request.Cookie) // attach refresh_token cookie + } + w := httptest.NewRecorder() + + r.ServeHTTP(w, req) + + t.Log(request.Name+" response:", w.Body.String()) + if !request.ignoreError { + assert.Equal(t, http.StatusOK, w.Code) + } + } +} diff --git a/models/event.go b/models/event.go index a73888a..96ea941 100644 --- a/models/event.go +++ b/models/event.go @@ -1,9 +1,9 @@ package models type Event struct { - Id int `json:"id"` - Name string `json:"name"` - Date string `json:"date"` - Attendees []Person `json:"attendees"` - Count int `json:"count"` + Id int `gorm:"primaryKey" json:"id"` + Name string `gorm:"column:name" json:"name"` + Date string `gorm:"column:date" json:"date"` + Attendees Persons `gorm:"type:json" json:"attendees"` + Count int `gorm:"column:count" json:"count"` } diff --git a/models/member.go b/models/member.go index 9739b33..46cfa3b 100644 --- a/models/member.go +++ b/models/member.go @@ -1,17 +1,20 @@ package models type Member struct { - Id int `json:"id,omitempty"` - FirstName string `json:"firstName,omitempty"` - LastName string `json:"lastName,omitempty"` - Birthday string `json:"birthday,omitempty"` - Address string `json:"address,omitempty"` - Zip string `json:"zip,omitempty"` - Town string `json:"town,omitempty"` - Phone string `json:"phone,omitempty"` - Email string `json:"email,omitempty"` - FirstVisit string `json:"firstVisit,omitempty"` - LastVisit string `json:"lastVisit,omitempty"` - Group string `json:"group,omitempty"` - ResponsiblePerson string `json:"responsiblePerson,omitempty"` + Id int `gorm:"primaryKey" json:"id"` + FirstName string `gorm:"column:firstName" json:"firstName,omitempty"` + FirstNameHash string `gorm:"column:firstNameHash" json:"-"` + LastName string `gorm:"column:lastName" json:"lastName,omitempty"` + LastNameHash string `gorm:"column:lastNameHash" json:"-"` + Birthday string `gorm:"column:birthday" json:"birthday,omitempty"` + BirthdayHash string `gorm:"column:birthdayHash" json:"-"` + Address string `gorm:"column:address" json:"address,omitempty"` + Zip string `gorm:"column:zip" json:"zip,omitempty"` + Town string `gorm:"column:town" json:"town,omitempty"` + Phone string `gorm:"column:phone" json:"phone,omitempty"` + Email string `gorm:"column:email" json:"email,omitempty"` + FirstVisit string `gorm:"column:firstVisit" json:"firstVisit,omitempty"` + LastVisit string `gorm:"column:lastVisit" json:"lastVisit,omitempty"` + Group string `gorm:"column:group" json:"group,omitempty"` + ResponsiblePerson string `gorm:"column:responsiblePerson" json:"responsiblePerson,omitempty"` } diff --git a/models/person.go b/models/person.go index 1517870..6b8be95 100644 --- a/models/person.go +++ b/models/person.go @@ -1,7 +1,29 @@ package models +import ( + "database/sql/driver" + "encoding/json" + "fmt" +) + +type Persons []Person + type Person struct { - Id int `json:"id,omitEmpty"` - FirstName string `json:"firstName"` - LastName string `json:"lastName"` + Id int `gorm:"primaryKey" json:"id"` + FirstName string `gorm:"column:firstName" json:"firstName"` + LastName string `gorm:"column:lastName" json:"lastName"` +} + +// --- Implement driver.Valuer (for saving to DB) +func (r Persons) Value() (driver.Value, error) { + return json.Marshal(r) +} + +// --- Implement sql.Scanner (for reading from DB) +func (r *Persons) Scan(value any) error { + bytes, ok := value.([]byte) + if !ok { + return fmt.Errorf("failed to unmarshal Settings: %v", value) + } + return json.Unmarshal(bytes, r) }