14 Commits

Author SHA1 Message Date
Adrian Zürcher
a938f41e94 add new function update event 2025-11-05 11:18:30 +01:00
Adrian Zürcher
1d09705adb fix wrong api call 2025-11-04 09:39:11 +01:00
Adrian Zürcher
4ee558b340 fix wrong api call 2025-11-04 09:38:35 +01:00
Adrian Zürcher
ad44a65087 update member from one to several ids 2025-11-04 09:35:52 +01:00
Adrian Zürcher
e3870de7d5 fix wrong mix of new current ids 2025-11-04 08:24:27 +01:00
Adrian Zürcher
25f965cd72 fix wrong event id 2025-11-04 08:20:15 +01:00
Adrian Zürcher
25f744b26c change error output of startnewevent 2025-11-01 23:22:46 +01:00
Adrian Zürcher
ec77c8a488 add exists check to events and fix wrong event name 2025-11-01 23:17:58 +01:00
Adrian Zürcher
80675ed328 implement dbhandler package and new test file 2025-10-31 08:11:07 +01:00
Adrian Zürcher
1568ee2482 add gitignore 2025-10-31 08:10:28 +01:00
Adrian Zürcher
6e3ff95e8c fix last comma error 2025-10-17 15:54:16 +02:00
Adrian Zürcher
5d0496f1db add new handlers for event and responsible tables 2025-10-17 15:41:13 +02:00
Adrian Zürcher
521896510d change module name to repo 2025-10-08 15:06:08 +02:00
Adrian Zürcher
c9577720e6 add README 2025-10-08 15:03:43 +02:00
21 changed files with 1312 additions and 697 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
*.db
*.log

View File

@@ -0,0 +1,50 @@
# 🧩 Member DB (Golang)
A lightweight and efficient **member management system** written in **Go**.
This project provides a RESTful API to perform essential operations like:
✅ **Add and manage members**
📅 **Track member attendance at events**
🗺️ **Record member excursions and outings**
Ideal for small organizations, clubs, or community groups looking for a minimal yet effective backend to handle membership data.
---
## 🚀 Features
- ⚡ Built using **Go** for high performance and simplicity
- 🌐 **RESTful API** endpoints
- 🧱 Modular structure for easy expansion
- 🪶 Lightweight — no unnecessary dependencies
---
## 🎯 Use Cases
- 🏫 Club membership tracking
- 🗓️ Attendance records for events, meetings, or workshops
- 🧭 Excursion or trip logging for community groups
---
## 🛠️ Tech Stack
- **Language:** Go (Golang)
- **Architecture:** RESTful API
- **Storage:** (Add your DB here, e.g., SQLite, PostgreSQL, or in-memory)
- **Deployment:** Runs easily on any Go-supported environment
---
## 📦 Getting Started
### Prerequisites
- Go 1.24.5+ installed
### Installation
```bash
git clone https://gitea.tecamino.com/paadi/memberDB.git
cd memberDB
go mod tidy
go run main.go

View File

@@ -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,23 +27,34 @@ 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("/members", apiHandler.GetMemberById)
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.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/edit", apiHandler.UpdateMember)
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,
}
}, nil
}
func (api *API) Run() error {

View File

@@ -1,19 +1,38 @@
package api
import (
"memberDB/handlers"
"memberDB/models"
"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) {
@@ -27,55 +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.CreateNewAttendanceTable(); 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.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",
})
}
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
}

View File

@@ -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)
}

189
api/eventHandler.go Normal file
View File

@@ -0,0 +1,189 @@
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.DBHandlerIsInitialized() {
a.logger.Error("StartNewEvent", "database handler is not initialized")
c.JSON(http.StatusInternalServerError, nil)
return
}
name := c.Query("name")
if name == "" {
a.logger.Error("StartNewEvent", "missing query 'name'")
c.JSON(http.StatusInternalServerError, nil)
return
}
if err := a.DbHandler.StartNewEvent(name); err != nil {
a.logger.Error("StartNewEvent", err)
c.JSON(http.StatusBadRequest, gin.H{"message": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "New Event added " + name,
})
}
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
}
var i int
var err error
id := c.Query("id")
if id != "" {
i, err = strconv.Atoi(id)
if err != nil {
a.logger.Error("GetEvent", err)
c.JSON(http.StatusInternalServerError, nil)
return
}
}
events, err := a.DbHandler.GetEvent(uint(i))
if err != nil {
a.logger.Error("GetEvent", err)
c.JSON(http.StatusBadRequest, nil)
return
}
c.JSON(http.StatusOK, events)
}
func (a *APIHandler) UpdateEvent(c *gin.Context) {
if !a.DBHandlerIsInitialized() {
a.logger.Error("UpdateEvent", "database handler is not initialized")
c.JSON(http.StatusInternalServerError, nil)
return
}
var events []models.Event
err := c.BindJSON(&events)
if err != nil {
a.logger.Error("UpdateEvent", err)
c.JSON(http.StatusInternalServerError, nil)
return
}
for _, event := range events {
err = a.DbHandler.UpdateEvent(event.Id, event)
if err != nil {
a.logger.Error("UpdateEvent", err)
c.JSON(http.StatusBadRequest, gin.H{
"message": err.Error(),
})
return
}
}
c.JSON(http.StatusOK, gin.H{
"message": "event(s) updated",
})
}
func (a *APIHandler) DeleteEvent(c *gin.Context) {
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 []uint `json:"ids"`
}
err = c.BindJSON(&request)
if err != nil {
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(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "event deleted",
})
}
func (a *APIHandler) AddNewAttendees(c *gin.Context) {
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 {
a.logger.Error("AddNewAttendees", err)
c.JSON(http.StatusInternalServerError, nil)
return
}
err = a.DbHandler.AddAttendeesToEvent(event)
if err != nil {
a.logger.Error("AddNewAttendees", err)
c.JSON(http.StatusInternalServerError, 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.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 {
a.logger.Error("DeleteAttendee", err)
c.JSON(http.StatusInternalServerError, nil)
return
}
err = a.DbHandler.DeleteAttendeesFromEvent(event)
if err != nil {
a.logger.Error("DeleteAttendee", err)
c.JSON(http.StatusInternalServerError, gin.H{
"message": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "attendee(s) deleted",
})
}

View File

@@ -4,10 +4,11 @@ import (
"encoding/csv"
"fmt"
"io"
"memberDB/models"
"net/http"
"strconv"
"gitea.tecamino.com/paadi/memberDB/models"
"github.com/gin-gonic/gin"
"github.com/saintfish/chardet"
"golang.org/x/text/encoding/charmap"
@@ -15,12 +16,19 @@ 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
c.BindJSON(&member)
err := c.BindJSON(&member)
if err != nil {
a.logger.Error("AddNewMember", err)
c.JSON(http.StatusInternalServerError, nil)
return
}
var text string
if member.FirstName == "" {
@@ -33,14 +41,16 @@ 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",
})
return
}
err := a.DbHandler.AddNewMember(member)
err = a.DbHandler.AddNewMember(member)
if err != nil {
a.logger.Error("AddNewMember", err)
c.JSON(http.StatusBadRequest, gin.H{
"message": err.Error(),
})
@@ -52,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
}
@@ -64,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(),
})
@@ -81,82 +93,80 @@ func (a *APIHandler) GetMemberById(c *gin.Context) {
c.JSON(http.StatusOK, members)
}
func (a *APIHandler) EditMember(c *gin.Context) {
if !a.databaseOpened(c) {
func (a *APIHandler) UpdateMember(c *gin.Context) {
if !a.DBHandlerIsInitialized() {
a.logger.Error("EditMember", "database handler is not initialized")
c.JSON(http.StatusInternalServerError, nil)
return
}
var i int
var err error
id := c.Query("id")
if id != "" {
i, err = strconv.Atoi(id)
var members []models.Member
err := c.BindJSON(&members)
if err != nil {
a.logger.Error("EditMember", err)
c.JSON(http.StatusInternalServerError, nil)
return
}
for _, member := range members {
err = a.DbHandler.UpdateMember(member.Id, member)
if err != nil {
a.logger.Error("EditMember", err)
c.JSON(http.StatusBadRequest, gin.H{
"message": err.Error(),
})
return
}
} else {
c.JSON(http.StatusBadRequest, gin.H{
"message": "query parameter 'id' missing",
})
return
}
var member models.Member
c.BindJSON(&member)
err = a.DbHandler.UpdateMember(i, member)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"message": err.Error(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "member updated",
"message": "member(s) updated",
})
}
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(),
})
@@ -175,7 +185,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
@@ -183,6 +194,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
}
@@ -193,6 +205,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
}
@@ -201,12 +214,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
}
@@ -221,6 +236,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
}
@@ -230,6 +246,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
}
@@ -285,6 +302,7 @@ func (a *APIHandler) ImportCSV(c *gin.Context) {
}
if message != "" {
a.logger.Error("ImportCSV", message)
c.JSON(http.StatusBadRequest, gin.H{
"message": message,
})

99
api/responsibleHandler.go Normal file
View File

@@ -0,0 +1,99 @@
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.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 {
a.logger.Error("AddNewResponsible", err)
c.JSON(http.StatusInternalServerError, nil)
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) 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
id := c.Query("id")
if id != "" {
i, err = strconv.Atoi(id)
if err != nil {
a.logger.Error("GetResponsible", err)
c.JSON(http.StatusInternalServerError, nil)
return
}
}
members, err := a.DbHandler.GetResponsible(uint(i))
if err != nil {
a.logger.Error("GetResponsible", err)
c.JSON(http.StatusBadRequest, gin.H{
"message": err.Error(),
})
return
}
c.JSON(http.StatusOK, members)
}
func (a *APIHandler) DeleteResponsible(c *gin.Context) {
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 []uint `json:"ids"`
}
err = c.BindJSON(&request)
if err != nil {
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(),
})
return
}
c.JSON(http.StatusOK, gin.H{
"message": "responsible(s) deleted",
})
}

Binary file not shown.

29
go.mod
View File

@@ -1,13 +1,15 @@
module memberDB
module gitea.tecamino.com/paadi/memberDB
go 1.24.5
require (
gitea.tecamino.com/paadi/dbHandler v1.0.4
gitea.tecamino.com/paadi/tecamino-logger v0.2.1
github.com/gin-contrib/cors v1.7.6
github.com/gin-gonic/gin v1.11.0
github.com/go-playground/assert/v2 v2.2.0
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
golang.org/x/text v0.27.0
modernc.org/sqlite v1.39.0
golang.org/x/text v0.30.0
)
require (
@@ -17,12 +19,16 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/glebarez/sqlite v1.11.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.27.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
@@ -37,16 +43,21 @@ require (
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
go.uber.org/mock v0.5.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/arch v0.20.0 // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/crypto v0.43.0 // indirect
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/mod v0.25.0 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/sync v0.16.0 // indirect
golang.org/x/sys v0.35.0 // indirect
golang.org/x/tools v0.34.0 // indirect
golang.org/x/mod v0.28.0 // indirect
golang.org/x/net v0.45.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/tools v0.37.0 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gorm.io/gorm v1.31.0 // indirect
modernc.org/libc v1.66.3 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.39.0 // indirect
)

50
go.sum
View File

@@ -1,3 +1,7 @@
gitea.tecamino.com/paadi/dbHandler v1.0.4 h1:ctnaec0GDdtw3gRQdUISVDYLJ9x+vt50VW41OemfhD4=
gitea.tecamino.com/paadi/dbHandler v1.0.4/go.mod h1:y/xn/POJg1DO++67uKvnO23lJQgh+XFQq7HZCS9Getw=
gitea.tecamino.com/paadi/tecamino-logger v0.2.1 h1:sQTBKYPdzn9mmWX2JXZBtGBvNQH7cuXIwsl4TD0aMgE=
gitea.tecamino.com/paadi/tecamino-logger v0.2.1/go.mod h1:FkzRTldUBBOd/iy2upycArDftSZ5trbsX5Ira5OzJgM=
github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ=
github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA=
github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
@@ -17,6 +21,10 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -36,6 +44,10 @@ github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17k
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
@@ -76,33 +88,43 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
modernc.org/cc/v4 v4.26.2 h1:991HMkLjJzYBIfha6ECZdjrIYz2/1ayr+FL8GN+CNzM=
modernc.org/cc/v4 v4.26.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=

View File

@@ -3,481 +3,36 @@ package handlers
import (
"crypto/hmac"
"crypto/sha256"
"database/sql"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"memberDB/crypto"
"memberDB/models"
"memberDB/utils"
"os"
"path/filepath"
"strings"
"time"
_ "modernc.org/sqlite"
"gitea.tecamino.com/paadi/dbHandler"
"gitea.tecamino.com/paadi/tecamino-logger/logging"
)
type DatabaseHandler struct {
database *sql.DB
database *dbHandler.DBHandler
token []byte
}
func NewDatabaseHandler(file string, create bool) (*DatabaseHandler, error) {
if create {
// createOrOpenDB creates or opens a SQLite database at the given path.
// Create the directory if it doesn't exist
dir := filepath.Dir(file)
func NewDatabaseHandler(name, path string, logger *logging.Logger) (*DatabaseHandler, error) {
err := os.MkdirAll(dir, os.ModePerm)
database, err := dbHandler.NewDBHandler(name, path, logger)
if err != nil {
return nil, fmt.Errorf("failed to create directory: %w", err)
return nil, err
}
} else {
if _, err := os.Stat(file); err != nil {
return nil, fmt.Errorf("%s not found", file)
}
}
// Open the database (creates it if it doesn't exist)
db, err := sql.Open("sqlite", file)
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
}
// Test the connection
if err = db.Ping(); err != nil {
return nil, fmt.Errorf("failed to connect to database: %w", err)
}
return &DatabaseHandler{database: db}, nil
return &DatabaseHandler{database: database}, nil
}
func (dh *DatabaseHandler) SetToken(token string) {
dh.token = []byte(token)
func (dH *DatabaseHandler) DatabaseOpened() bool {
return dH.database != nil
}
// 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) AddNewTable(model any) error {
return dH.database.AddNewTable(model)
}
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) AddNewColum(model any) error {
return dH.database.AddNewColum(model)
}
func (dh *DatabaseHandler) hashField(field string) string {
@@ -486,49 +41,6 @@ func (dh *DatabaseHandler) hashField(field string) string {
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
func (dh *DatabaseHandler) SetToken(token string) {
dh.token = []byte(token)
}

106
handlers/events.go Normal file
View File

@@ -0,0 +1,106 @@
package handlers
import (
"errors"
"slices"
"time"
"gitea.tecamino.com/paadi/memberDB/models"
)
func (dh *DatabaseHandler) StartNewEvent(name string) error {
if !dh.DatabaseOpened() {
return errors.New("database not opened")
}
if nil == dh.database.Exists(&models.Event{}, "name", name, false) {
return errors.New("event with name: " + name + " exists already")
}
return dh.database.AddNewColum(&models.Event{
Name: name,
Date: time.Now().Format("2006-01-02 15:04:05"),
})
}
func (dh *DatabaseHandler) GetEvent(id uint) (event []models.Event, err error) {
if !dh.DatabaseOpened() {
return event, errors.New("database not opened")
}
err = dh.database.GetById(&event, id)
if err != nil {
return
}
for i := range event {
event[i].Count = len(event[i].Attendees)
}
return
}
func (dh *DatabaseHandler) UpdateEvent(id int, event models.Event) (err error) {
if !dh.DatabaseOpened() {
return errors.New("database not opened")
}
return dh.database.UpdateValuesById(&event, uint(event.Id))
}
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")
}
return dh.database.DeleteById(&models.Event{}, ids...)
}
func (dh *DatabaseHandler) AddAttendeesToEvent(newEvent models.Event) error {
if !dh.DatabaseOpened() {
return errors.New("database not opened")
}
var event models.Event
err := dh.database.GetById(&event, uint(newEvent.Id))
if err != nil {
return err
}
next:
for _, newAttendee := range newEvent.Attendees {
for _, attendee := range event.Attendees {
if attendee.FirstName == newAttendee.FirstName && attendee.LastName == newAttendee.LastName {
continue next
}
}
event.Attendees = append(event.Attendees, newAttendee)
}
event.Count = len(event.Attendees)
return dh.database.UpdateValuesById(&event, uint(newEvent.Id))
}
func (dh *DatabaseHandler) DeleteAttendeesFromEvent(newEvent models.Event) error {
if !dh.DatabaseOpened() {
return errors.New("database not opened")
}
var event models.Event
dh.database.GetById(&event, uint(newEvent.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
}
}
}
event.Count = len(event.Attendees)
return dh.database.UpdateValuesById(&event, uint(newEvent.Id))
}

323
handlers/members.go Normal file
View File

@@ -0,0 +1,323 @@
package handlers
import (
"errors"
"fmt"
"time"
"gitea.tecamino.com/paadi/memberDB/crypto"
"gitea.tecamino.com/paadi/memberDB/models"
"gitea.tecamino.com/paadi/memberDB/utils"
)
// 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)
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
}
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
}
}
return nil
}
// DeleteMember removes members by given ids
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")
}
return dh.database.DeleteById(&models.Member{}, ids...)
}
// GetMember returns one member by given id
func (dh *DatabaseHandler) GetMember(id uint) (members []models.Member, err error) {
if !dh.DatabaseOpened() {
return members, errors.New("database not opened")
}
err = dh.database.GetById(&members, id)
if err != nil {
return
}
for i := range members {
members[i].FirstName, err = crypto.Decrypt(members[i].FirstName, dh.token)
if err != nil {
return
}
members[i].LastName, err = crypto.Decrypt(members[i].LastName, dh.token)
if err != nil {
return
}
members[i].Birthday, err = crypto.Decrypt(members[i].Birthday, dh.token)
if err != nil {
return
}
members[i].Address, err = crypto.Decrypt(members[i].Address, dh.token)
if err != nil {
return
}
members[i].Zip, err = crypto.Decrypt(members[i].Zip, dh.token)
if err != nil {
return
}
members[i].Town, err = crypto.Decrypt(members[i].Town, dh.token)
if err != nil {
return
}
members[i].Phone, err = crypto.Decrypt(members[i].Phone, dh.token)
if err != nil {
return
}
members[i].Email, err = crypto.Decrypt(members[i].Email, dh.token)
if err != nil {
return
}
members[i].FirstVisit, err = crypto.Decrypt(members[i].FirstVisit, dh.token)
if err != nil {
return
}
members[i].LastVisit, err = crypto.Decrypt(members[i].LastVisit, dh.token)
if err != nil {
return
}
members[i].Group, err = crypto.Decrypt(members[i].Group, dh.token)
if err != nil {
return
}
members[i].ResponsiblePerson, err = crypto.Decrypt(members[i].ResponsiblePerson, dh.token)
if err != nil {
return
}
}
return
}
// UpdateMember updates/overrides all information given meber id
func (dh *DatabaseHandler) UpdateMember(id int, member models.Member) (err error) {
if !dh.DatabaseOpened() {
return errors.New("database not opened")
}
if member.FirstName != "" {
member.FirstNameHash = dh.hashField(member.FirstName)
member.FirstName, err = crypto.Encrypt(member.FirstName, dh.token)
if err != nil {
return
}
}
if member.LastName != "" {
member.LastNameHash = dh.hashField(member.LastName)
member.LastName, err = crypto.Encrypt(member.LastName, dh.token)
if err != nil {
return
}
}
//check correct birtday format
if member.Birthday != "" && utils.IsValidBirthday(member.Birthday) {
member.BirthdayHash = dh.hashField(member.BirthdayHash)
member.Birthday, err = crypto.Encrypt(member.Birthday, dh.token)
if err != nil {
return err
}
} else if member.Birthday != "" {
return errors.New("incorrect birthday format")
}
if member.Address != "" {
member.Address, err = crypto.Encrypt(member.Address, dh.token)
if err != nil {
return err
}
}
if member.Zip != "" {
member.Zip, err = crypto.Encrypt(member.Zip, dh.token)
if err != nil {
return err
}
}
if member.Town != "" {
member.Town, err = crypto.Encrypt(member.Town, dh.token)
if err != nil {
return err
}
}
if member.Phone != "" {
member.Phone, err = crypto.Encrypt(member.Phone, dh.token)
if err != nil {
return err
}
}
//check correct email format
if member.Email != "" && utils.IsValidEmail(member.Email) {
member.Email, err = crypto.Encrypt(member.Email, dh.token)
if err != nil {
return err
}
} else if member.Email != "" {
return errors.New("incorrect email format")
}
if member.FirstVisit != "" {
member.FirstVisit, err = crypto.Encrypt(member.FirstVisit, dh.token)
if err != nil {
return err
}
}
if member.LastVisit != "" {
member.LastVisit, err = crypto.Encrypt(member.LastVisit, dh.token)
if err != nil {
return err
}
}
if member.Group != "" {
member.Group, err = crypto.Encrypt(member.Group, dh.token)
if err != nil {
return err
}
}
if member.ResponsiblePerson != "" {
member.ResponsiblePerson, err = crypto.Encrypt(member.ResponsiblePerson, dh.token)
if err != nil {
return err
}
}
return dh.database.UpdateValuesById(&member, uint(member.Id))
}
// memberExists helper to check wheter member already exists
func (dh *DatabaseHandler) memberExists(checkMember models.Member) (bool, error) {
if !dh.DatabaseOpened() {
return false, errors.New("database not opened")
}
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
}

64
handlers/responsible.go Normal file
View File

@@ -0,0 +1,64 @@
package handlers
import (
"errors"
"gitea.tecamino.com/paadi/memberDB/models"
)
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)
if err != nil {
return err
} else if exists {
continue
}
err = dh.database.AddNewColum(&responsible)
if err != nil {
return err
}
}
return nil
}
func (dh *DatabaseHandler) GetResponsible(id uint) (persons []models.Person, err error) {
if !dh.DatabaseOpened() {
return persons, errors.New("database not opened")
}
err = dh.database.GetById(&persons, id)
return
}
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")
}
return dh.database.DeleteById(&models.Person{}, ids...)
}
func (dh *DatabaseHandler) responsibleExists(CheckResponsible models.Person) (bool, error) {
if !dh.DatabaseOpened() {
return false, errors.New("database not opened")
}
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
}

12
main.go
View File

@@ -3,7 +3,8 @@ package main
import (
"flag"
"log"
"memberDB/api"
"gitea.tecamino.com/paadi/memberDB/api"
)
func main() {
@@ -11,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)
}
}

220
member_test.go Normal file
View File

@@ -0,0 +1,220 @@
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.UpdateMember)
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("/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.UpdateMember)
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("/events/edit", apiHandler.UpdateEvent)
v1.POST("/events/delete", apiHandler.DeleteEvent)
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)
}
}
}

View File

@@ -1,6 +0,0 @@
package models
type Attendees struct {
FirstName int `json:"firstName"`
LastName string `json:"lastName"`
}

View File

@@ -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 `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"`
}

View File

@@ -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"`
}

29
models/person.go Normal file
View File

@@ -0,0 +1,29 @@
package models
import (
"database/sql/driver"
"encoding/json"
"fmt"
)
type Persons []Person
type Person struct {
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)
}