From 5d0496f1db5a1e9e7e64afa8a50e8b5008088065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Z=C3=BCrcher?= Date: Fri, 17 Oct 2025 15:41:13 +0200 Subject: [PATCH] add new handlers for event and responsible tables --- api/api.go | 10 +- api/apiHandler.go | 10 +- api/attendanceHandler.go | 63 ----- api/eventHandler.go | 148 ++++++++++++ api/membersHandler.go | 18 +- api/responsibleHandler.go | 95 ++++++++ handlers/database.go | 477 -------------------------------------- handlers/events.go | 170 ++++++++++++++ handlers/members.go | 421 +++++++++++++++++++++++++++++++++ handlers/responsible.go | 110 +++++++++ models/attendees.go | 6 - models/event.go | 10 +- models/person.go | 7 + 13 files changed, 989 insertions(+), 556 deletions(-) delete mode 100644 api/attendanceHandler.go create mode 100644 api/eventHandler.go create mode 100644 api/responsibleHandler.go create mode 100644 handlers/events.go create mode 100644 handlers/members.go create mode 100644 handlers/responsible.go delete mode 100644 models/attendees.go create mode 100644 models/person.go diff --git a/api/api.go b/api/api.go index 5ad0590..95ff57a 100644 --- a/api/api.go +++ b/api/api.go @@ -29,9 +29,11 @@ func NewAPI(host string, port int) *API { apiHandler := NewAPIHandler() v1 := r.Group("v1") - v1.GET("/members", apiHandler.GetMemberById) v1.GET("/events", apiHandler.GetEventById) v1.GET("/events/new", apiHandler.StartNewEvent) + v1.GET("/events/delete", apiHandler.DeleteEvent) + v1.GET("/members", apiHandler.GetMemberById) + v1.GET("/responsible", apiHandler.GetResponsibleById) v1.POST("/database/open", apiHandler.OpenDatabase) v1.POST("/members/add", apiHandler.AddNewMember) @@ -39,6 +41,12 @@ func NewAPI(host string, port int) *API { 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) + return &API{host: host, port: port, router: r, diff --git a/api/apiHandler.go b/api/apiHandler.go index 573c63a..e991f3c 100644 --- a/api/apiHandler.go +++ b/api/apiHandler.go @@ -50,7 +50,14 @@ func (a *APIHandler) OpenDatabase(c *gin.Context) { return } - if err := a.DbHandler.CreateNewAttendanceTable(); err != nil { + if err := a.DbHandler.CreateNewEventTable(); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + if err := a.DbHandler.CreateNewResponsibleTable(); err != nil { c.JSON(http.StatusBadRequest, gin.H{ "message": err.Error(), }) @@ -71,6 +78,7 @@ func (a *APIHandler) OpenDatabase(c *gin.Context) { }) } +// 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{ diff --git a/api/attendanceHandler.go b/api/attendanceHandler.go deleted file mode 100644 index a6a48df..0000000 --- a/api/attendanceHandler.go +++ /dev/null @@ -1,63 +0,0 @@ -package api - -import ( - "net/http" - "strconv" - - "github.com/gin-gonic/gin" -) - -func (a *APIHandler) StartNewEvent(c *gin.Context) { - if !a.databaseOpened(c) { - return - } - - name := c.Query("name") - if name == "" { - c.JSON(http.StatusBadRequest, gin.H{ - "message": "missing query 'name'", - }) - return - } - - if err := a.DbHandler.StartNewEvent(name); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "message": "New Event added " + name, - }) -} - -func (a *APIHandler) GetEventById(c *gin.Context) { - if !a.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 - } - } - - events, err := a.DbHandler.GetEvent(i) - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "message": err.Error(), - }) - return - } - - c.JSON(http.StatusOK, events) -} diff --git a/api/eventHandler.go b/api/eventHandler.go new file mode 100644 index 0000000..1466722 --- /dev/null +++ b/api/eventHandler.go @@ -0,0 +1,148 @@ +package api + +import ( + "net/http" + "strconv" + + "gitea.tecamino.com/paadi/memberDB/models" + "github.com/gin-gonic/gin" +) + +func (a *APIHandler) StartNewEvent(c *gin.Context) { + if !a.databaseOpened(c) { + return + } + + name := c.Query("name") + if name == "" { + c.JSON(http.StatusBadRequest, gin.H{ + "message": "missing query 'name'", + }) + return + } + + if err := a.DbHandler.StartNewEvent(name); err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "New Event added " + name, + }) +} + +func (a *APIHandler) GetEventById(c *gin.Context) { + if !a.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 + } + } + + events, err := a.DbHandler.GetEvent(i) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, events) +} + +func (a *APIHandler) DeleteEvent(c *gin.Context) { + if !a.databaseOpened(c) { + return + } + var err error + + var request struct { + Ids []int `json:"ids"` + } + err = c.BindJSON(&request) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + err = a.DbHandler.DeleteEvent(request.Ids...) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, gin.H{ + "message": "event deleted", + }) +} + +func (a *APIHandler) AddNewAttendees(c *gin.Context) { + if !a.databaseOpened(c) { + return + } + + var event models.Event + err := c.BindJSON(&event) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + err = a.DbHandler.AddAttendeesToEvent(event) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "attendee(s) added", + }) +} + +func (a *APIHandler) DeleteAttendee(c *gin.Context) { + if !a.databaseOpened(c) { + return + } + var err error + + var event models.Event + err = c.BindJSON(&event) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + err = a.DbHandler.DeleteAttendeesFromEvent(event) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "attendee(s) deleted", + }) +} diff --git a/api/membersHandler.go b/api/membersHandler.go index 25fcaf3..4329ab1 100644 --- a/api/membersHandler.go +++ b/api/membersHandler.go @@ -21,7 +21,13 @@ func (a *APIHandler) AddNewMember(c *gin.Context) { } var member models.Member - c.BindJSON(&member) + err := c.BindJSON(&member) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } var text string if member.FirstName == "" { @@ -40,7 +46,7 @@ func (a *APIHandler) AddNewMember(c *gin.Context) { return } - err := a.DbHandler.AddNewMember(member) + err = a.DbHandler.AddNewMember(member) if err != nil { c.JSON(http.StatusBadRequest, gin.H{ "message": err.Error(), @@ -107,7 +113,13 @@ func (a *APIHandler) EditMember(c *gin.Context) { } var member models.Member - c.BindJSON(&member) + err = c.BindJSON(&member) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } err = a.DbHandler.UpdateMember(i, member) if err != nil { diff --git a/api/responsibleHandler.go b/api/responsibleHandler.go new file mode 100644 index 0000000..4997aff --- /dev/null +++ b/api/responsibleHandler.go @@ -0,0 +1,95 @@ +package api + +import ( + "net/http" + "strconv" + + "gitea.tecamino.com/paadi/memberDB/models" + + "github.com/gin-gonic/gin" +) + +func (a *APIHandler) AddNewResponsible(c *gin.Context) { + if !a.databaseOpened(c) { + return + } + + var responsible models.Person + err := c.BindJSON(&responsible) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + err = a.DbHandler.AddNewResponsible(responsible) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "responsible added", + }) +} + +func (a *APIHandler) GetResponsibleById(c *gin.Context) { + if !a.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 + } + } + + members, err := a.DbHandler.GetResponsible(i) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, members) +} + +func (a *APIHandler) DeleteResponsible(c *gin.Context) { + if !a.databaseOpened(c) { + return + } + var err error + + var request struct { + Ids []int `json:"ids"` + } + err = c.BindJSON(&request) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + + err = a.DbHandler.DeleteResponsible(request.Ids...) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{ + "message": err.Error(), + }) + return + } + c.JSON(http.StatusOK, gin.H{ + "message": "responsible(s) deleted", + }) +} diff --git a/handlers/database.go b/handlers/database.go index a4cf81f..1879e42 100644 --- a/handlers/database.go +++ b/handlers/database.go @@ -5,17 +5,9 @@ import ( "crypto/sha256" "database/sql" "encoding/hex" - "encoding/json" - "errors" "fmt" "os" "path/filepath" - "strings" - "time" - - "gitea.tecamino.com/paadi/memberDB/crypto" - "gitea.tecamino.com/paadi/memberDB/models" - "gitea.tecamino.com/paadi/memberDB/utils" _ "modernc.org/sqlite" ) @@ -59,477 +51,8 @@ func (dh *DatabaseHandler) SetToken(token string) { dh.token = []byte(token) } -// createExampleTable creates a new 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 -} - -func (dh *DatabaseHandler) CreateNewAttendanceTable() error { - createTableSQL := ` - CREATE TABLE IF NOT EXISTS attendance ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - date TEXT UNIQUE NOT NULL, - attendees TEXT NOT NULL, - count INTEGER NOT NULL - );` - - _, err := dh.database.Exec(createTableSQL) - if err != nil { - return fmt.Errorf("failed to create table: %w", err) - } - return nil -} - -func (dh *DatabaseHandler) AddNewMember(members ...models.Member) error { - for _, member := range members { - - exists, err := dh.memberExists(member) - if err != nil { - return err - } else if exists { - return fmt.Errorf("member %s %s %s exists already", member.FirstName, member.LastName, member.Birthday) - } - - encFirstName, err := crypto.Encrypt(member.FirstName, dh.token) - if err != nil { - return err - } - - encLastName, err := crypto.Encrypt(member.LastName, dh.token) - if err != nil { - return err - } - - //check correct birtday format - if member.Birthday != "" && !utils.IsValidBirthday(member.Birthday) { - return errors.New("incorrect birthday format") - } - - encBirthday, err := crypto.Encrypt(member.Birthday, dh.token) - if err != nil { - return err - } - - encAddress, err := crypto.Encrypt(member.Address, dh.token) - if err != nil { - return err - } - - encZip, err := crypto.Encrypt(member.Zip, dh.token) - if err != nil { - return err - } - - encTown, err := crypto.Encrypt(member.Town, dh.token) - if err != nil { - return err - } - - encPhone, err := crypto.Encrypt(member.Phone, dh.token) - if err != nil { - return err - } - - //check correct email format - if member.Email != "" && !utils.IsValidEmail(member.Email) { - return errors.New("incorrect email format") - } - - encEmail, err := crypto.Encrypt(member.Email, dh.token) - if err != nil { - return err - } - - now := time.Now().Format("2006-01-02 15:04:05") - encFirstVisit, err := crypto.Encrypt(now, dh.token) - if err != nil { - return err - } - - encLastVisit, err := crypto.Encrypt(now, dh.token) - if err != nil { - return err - } - - encGroup, err := crypto.Encrypt(member.Group, dh.token) - if err != nil { - return err - } - - encResponsiblePerson, err := crypto.Encrypt(member.ResponsiblePerson, dh.token) - if err != nil { - 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) - if err != nil { - return err - } - } - return nil -} - -func (dh *DatabaseHandler) DeleteMember(ids ...int) error { - if len(ids) == 0 { - return errors.New("no ids given to be deleted") - } - - placeholders := make([]string, len(ids)) - args := make([]interface{}, 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 -} - -func (dh *DatabaseHandler) GetMember(id int) (members []models.Member, err error) { - query := `SELECT id, first_name, last_name, birthday, address, zip_code, town, phone, email, first_visit, last_visit, member_group, responsible_person FROM members` - var args any - if id > 0 { - query = ` - SELECT id, first_name, last_name, birthday, address, zip_code, town, phone, email, first_visit, last_visit, member_group, responsible_person FROM members - WHERE id = ? - ` - args = id - } - - rows, err := dh.database.Query(query, args) - 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 { - return - } - - firstName, err := crypto.Decrypt(encFirstName, dh.token) - if err != nil { - return members, err - } - - lastName, err := crypto.Decrypt(encLastName, dh.token) - if err != nil { - return members, err - } - - birthday, err := crypto.Decrypt(encBirthday, dh.token) - if err != nil { - return members, err - } - - address, err := crypto.Decrypt(encAddress, dh.token) - if err != nil { - return members, err - } - - zip, err := crypto.Decrypt(encZip, dh.token) - if err != nil { - return members, err - } - - town, err := crypto.Decrypt(encTown, dh.token) - if err != nil { - return members, err - } - - phone, err := crypto.Decrypt(encPhone, dh.token) - if err != nil { - return members, err - } - - email, err := crypto.Decrypt(encEmail, dh.token) - if err != nil { - return members, err - } - - firstVisit, err := crypto.Decrypt(encFirstVisit, dh.token) - if err != nil { - return members, err - } - - lastVisit, err := crypto.Decrypt(encLastVisit, dh.token) - if err != nil { - return members, err - } - - group, err := crypto.Decrypt(encGroup, dh.token) - if err != nil { - return members, err - } - - 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 -} - -func (dh *DatabaseHandler) UpdateMember(id int, member models.Member) (err error) { - var queryParameters []string - var args []any - - if member.FirstName != "" { - encFirstName, err := crypto.Encrypt(member.FirstName, dh.token) - if err != nil { - return err - } - - 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) - if err != nil { - return err - } - - 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) - 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) - if err != nil { - return err - } - queryParameters = append(queryParameters, "address = ?") - - args = append(args, encAddress) - } - - if member.Zip != "" { - encZip, 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) - if err != nil { - return err - } - queryParameters = append(queryParameters, "town = ?") - - args = append(args, encTown) - } - - if member.Phone != "" { - encPhone, 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) - 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) - 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) - 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) - 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) - 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 -} - -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 - } - - return true, nil // match found -} - func (dh *DatabaseHandler) hashField(field string) string { h := hmac.New(sha256.New, dh.token) h.Write([]byte(field)) return hex.EncodeToString(h.Sum(nil)) } - -func (dh *DatabaseHandler) StartNewEvent(name string) error { - _, err := dh.database.Exec("INSERT INTO attendance (name, date, attendees, count) VALUES (?, ?, ?, ?)", name, time.Now().Format("2006-01-02 15:04:05"), "[]", 0) - return err -} - -func (dh *DatabaseHandler) GetEvent(id int) (attendees []models.Event, err error) { - query := `SELECT id, name, date, attendees, count FROM attendance` - var args any - if id > 0 { - query = ` - SELECT id, name, date, attendees, count FROM attendance - WHERE id = ? - ` - args = id - } - - rows, err := dh.database.Query(query, args) - 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.Attendees - - 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, - }) - } - - return attendees, nil -} diff --git a/handlers/events.go b/handlers/events.go new file mode 100644 index 0000000..a3685c6 --- /dev/null +++ b/handlers/events.go @@ -0,0 +1,170 @@ +package handlers + +import ( + "encoding/json" + "errors" + "fmt" + "slices" + "strings" + "time" + + "gitea.tecamino.com/paadi/memberDB/models" +) + +func (dh *DatabaseHandler) CreateNewEventTable() error { + createTableSQL := ` + CREATE TABLE IF NOT EXISTS events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + date TEXT UNIQUE NOT NULL, + attendees TEXT NOT NULL, + count INTEGER NOT NULL + );` + + _, err := dh.database.Exec(createTableSQL) + if err != nil { + return fmt.Errorf("failed to create table: %w", err) + } + return nil +} + +func (dh *DatabaseHandler) StartNewEvent(name string) error { + _, err := dh.database.Exec("INSERT INTO events (name, date, attendees, count) VALUES (?, ?, ?, ?)", name, time.Now().Format("2006-01-02 15:04:05"), "[]", 0) + return err +} + +func (dh *DatabaseHandler) GetEvent(id int) (attendees []models.Event, err error) { + var args any + query := `SELECT id, name, date, attendees, count FROM events` + + if id > 0 { + query += ` WHERE id = ?` + args = id + } + + rows, err := dh.database.Query(query, args) + 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, + }) + } + + return attendees, nil +} + +func (dh *DatabaseHandler) DeleteEvent(ids ...int) error { + 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 +} + +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 + } + fmt.Println(attendeesString) + + var attendees []models.Person + err := json.Unmarshal([]byte(attendeesString), &attendees) + if err != nil { + return err + } + +next: + for _, newAttendee := range event.Attendees { + for _, attendee := range attendees { + if attendee.FirstName == newAttendee.FirstName && attendee.LastName == newAttendee.LastName { + continue next + } + } + attendees = append(attendees, newAttendee) + } + + 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 +} + +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 + } + + 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) + } + } + } + + 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 +} diff --git a/handlers/members.go b/handlers/members.go new file mode 100644 index 0000000..2be82e2 --- /dev/null +++ b/handlers/members.go @@ -0,0 +1,421 @@ +package handlers + +import ( + "database/sql" + "errors" + "fmt" + "strings" + "time" + + "gitea.tecamino.com/paadi/memberDB/crypto" + "gitea.tecamino.com/paadi/memberDB/models" + "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 { + for _, member := range members { + + exists, err := dh.memberExists(member) + if err != nil { + return err + } else if exists { + return fmt.Errorf("member %s %s %s exists already", member.FirstName, member.LastName, member.Birthday) + } + + encFirstName, err := crypto.Encrypt(member.FirstName, dh.token) + if err != nil { + return err + } + + encLastName, err := crypto.Encrypt(member.LastName, dh.token) + if err != nil { + return err + } + + //check correct birtday format + if member.Birthday != "" && !utils.IsValidBirthday(member.Birthday) { + return errors.New("incorrect birthday format") + } + + encBirthday, err := crypto.Encrypt(member.Birthday, dh.token) + if err != nil { + return err + } + + encAddress, err := crypto.Encrypt(member.Address, dh.token) + if err != nil { + return err + } + + encZip, err := crypto.Encrypt(member.Zip, dh.token) + if err != nil { + return err + } + + encTown, err := crypto.Encrypt(member.Town, dh.token) + if err != nil { + return err + } + + encPhone, err := crypto.Encrypt(member.Phone, dh.token) + if err != nil { + return err + } + + //check correct email format + if member.Email != "" && !utils.IsValidEmail(member.Email) { + return errors.New("incorrect email format") + } + + encEmail, err := crypto.Encrypt(member.Email, dh.token) + if err != nil { + return err + } + + now := time.Now().Format("2006-01-02 15:04:05") + encFirstVisit, err := crypto.Encrypt(now, dh.token) + if err != nil { + return err + } + + encLastVisit, err := crypto.Encrypt(now, dh.token) + if err != nil { + return err + } + + encGroup, err := crypto.Encrypt(member.Group, dh.token) + if err != nil { + return err + } + + encResponsiblePerson, err := crypto.Encrypt(member.ResponsiblePerson, dh.token) + if err != nil { + 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) + if err != nil { + return err + } + } + return nil +} + +// DeleteMember removes members by given ids +func (dh *DatabaseHandler) DeleteMember(ids ...int) error { + 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 +} + +// 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 + } + + rows, err := dh.database.Query(query, args) + 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 { + return + } + + firstName, err := crypto.Decrypt(encFirstName, dh.token) + if err != nil { + return members, err + } + + lastName, err := crypto.Decrypt(encLastName, dh.token) + if err != nil { + return members, err + } + + birthday, err := crypto.Decrypt(encBirthday, dh.token) + if err != nil { + return members, err + } + + address, err := crypto.Decrypt(encAddress, dh.token) + if err != nil { + return members, err + } + + zip, err := crypto.Decrypt(encZip, dh.token) + if err != nil { + return members, err + } + + town, err := crypto.Decrypt(encTown, dh.token) + if err != nil { + return members, err + } + + phone, err := crypto.Decrypt(encPhone, dh.token) + if err != nil { + return members, err + } + + email, err := crypto.Decrypt(encEmail, dh.token) + if err != nil { + return members, err + } + + firstVisit, err := crypto.Decrypt(encFirstVisit, dh.token) + if err != nil { + return members, err + } + + lastVisit, err := crypto.Decrypt(encLastVisit, dh.token) + if err != nil { + return members, err + } + + group, err := crypto.Decrypt(encGroup, dh.token) + if err != nil { + return members, err + } + + 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 +} + +// 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 member.FirstName != "" { + encFirstName, err := crypto.Encrypt(member.FirstName, dh.token) + if err != nil { + return err + } + + 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) + if err != nil { + return err + } + + 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) + 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) + if err != nil { + return err + } + queryParameters = append(queryParameters, "address = ?") + + args = append(args, encAddress) + } + + if member.Zip != "" { + encZip, 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) + if err != nil { + return err + } + queryParameters = append(queryParameters, "town = ?") + + args = append(args, encTown) + } + + if member.Phone != "" { + encPhone, 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) + 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) + 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) + 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) + 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) + 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 +} + +// 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 + } + + return true, nil // match found +} diff --git a/handlers/responsible.go b/handlers/responsible.go new file mode 100644 index 0000000..e99ba93 --- /dev/null +++ b/handlers/responsible.go @@ -0,0 +1,110 @@ +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 { + for _, responsible := range responsibles { + + exists, err := dh.responsibleExists(responsible) + if err != nil { + return err + } else if exists { + continue + } + + _, err = dh.database.Exec("INSERT INTO responsible (first_name, last_name) VALUES (?, ?)", responsible.FirstName, responsible.LastName) + if err != nil { + return err + } + } + 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 + } + + 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 +} + +func (dh *DatabaseHandler) DeleteResponsible(ids ...int) error { + 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 +} + +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 + } + + return true, nil // match found +} diff --git a/models/attendees.go b/models/attendees.go deleted file mode 100644 index 9c968b0..0000000 --- a/models/attendees.go +++ /dev/null @@ -1,6 +0,0 @@ -package models - -type Attendees struct { - FirstName int `json:"firstName"` - LastName string `json:"lastName"` -} diff --git a/models/event.go b/models/event.go index d820126..a73888a 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 []Attendees `json:"attendees"` - Count int `json:"count"` + Id int `json:"id"` + Name string `json:"name"` + Date string `json:"date"` + Attendees []Person `json:"attendees"` + Count int `json:"count"` } diff --git a/models/person.go b/models/person.go new file mode 100644 index 0000000..1517870 --- /dev/null +++ b/models/person.go @@ -0,0 +1,7 @@ +package models + +type Person struct { + Id int `json:"id,omitEmpty"` + FirstName string `json:"firstName"` + LastName string `json:"lastName"` +}