first commit
This commit is contained in:
50
api/api.go
Normal file
50
api/api.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type API struct {
|
||||
host string
|
||||
port int
|
||||
router *gin.Engine
|
||||
}
|
||||
|
||||
func NewAPI(host string, port int) *API {
|
||||
r := gin.Default()
|
||||
r.Use(cors.New(cors.Config{
|
||||
//AllowOrigins: []string{"http://localhost:9000"}, // frontend origin
|
||||
AllowOrigins: []string{"*"}, // frontend origin
|
||||
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
|
||||
ExposeHeaders: []string{"Content-Length"},
|
||||
AllowCredentials: true,
|
||||
MaxAge: 12 * time.Hour,
|
||||
}))
|
||||
|
||||
apiHandler := NewAPIHandler()
|
||||
|
||||
v1 := r.Group("v1")
|
||||
v1.GET("/members", apiHandler.GetMemberById)
|
||||
v1.GET("/events", apiHandler.GetEventById)
|
||||
v1.GET("/events/new", apiHandler.StartNewEvent)
|
||||
|
||||
v1.POST("/database/open", apiHandler.OpenDatabase)
|
||||
v1.POST("/members/add", apiHandler.AddNewMember)
|
||||
v1.POST("/members/edit", apiHandler.EditMember)
|
||||
v1.POST("/members/delete", apiHandler.DeleteMember)
|
||||
v1.POST("/members/import/csv", apiHandler.ImportCSV)
|
||||
|
||||
return &API{host: host,
|
||||
port: port,
|
||||
router: r,
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) Run() error {
|
||||
return api.router.Run(fmt.Sprintf("%s:%d", api.host, api.port))
|
||||
}
|
81
api/apiHandler.go
Normal file
81
api/apiHandler.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"memberDB/handlers"
|
||||
"memberDB/models"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type APIHandler struct {
|
||||
DbHandler *handlers.DatabaseHandler
|
||||
}
|
||||
|
||||
func NewAPIHandler() *APIHandler {
|
||||
return &APIHandler{}
|
||||
}
|
||||
|
||||
func (a *APIHandler) OpenDatabase(c *gin.Context) {
|
||||
var database models.Database
|
||||
var err error
|
||||
if err := c.BindJSON(&database); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if database.Path == "" {
|
||||
database.SetDefaultPath()
|
||||
}
|
||||
|
||||
if database.Token == "" {
|
||||
database.SetDefaultToken()
|
||||
}
|
||||
|
||||
a.DbHandler, err = handlers.NewDatabaseHandler(database.Path, database.Create)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.DbHandler.CreateNewMemberTable(); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := a.DbHandler.CreateNewAttendanceTable(); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
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
|
||||
}
|
63
api/attendanceHandler.go
Normal file
63
api/attendanceHandler.go
Normal file
@@ -0,0 +1,63 @@
|
||||
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)
|
||||
}
|
296
api/membersHandler.go
Normal file
296
api/membersHandler.go
Normal file
@@ -0,0 +1,296 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"io"
|
||||
"memberDB/models"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/saintfish/chardet"
|
||||
"golang.org/x/text/encoding/charmap"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
func (a *APIHandler) AddNewMember(c *gin.Context) {
|
||||
if !a.databaseOpened(c) {
|
||||
return
|
||||
}
|
||||
|
||||
var member models.Member
|
||||
c.BindJSON(&member)
|
||||
|
||||
var text string
|
||||
if member.FirstName == "" {
|
||||
text = "firstName "
|
||||
}
|
||||
if member.LastName == "" {
|
||||
text += "lastName "
|
||||
}
|
||||
if member.Birthday == "" {
|
||||
text += "birthday "
|
||||
}
|
||||
if text != "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"message": text + "can not be empty",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err := a.DbHandler.AddNewMember(member)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "member added",
|
||||
})
|
||||
}
|
||||
|
||||
func (a *APIHandler) GetMemberById(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.GetMember(i)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, members)
|
||||
}
|
||||
|
||||
func (a *APIHandler) EditMember(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
|
||||
}
|
||||
} 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",
|
||||
})
|
||||
}
|
||||
|
||||
func (a *APIHandler) DeleteMember(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.DeleteMember(request.Ids...)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "member deleted",
|
||||
})
|
||||
}
|
||||
|
||||
func (a *APIHandler) ImportCSV(c *gin.Context) {
|
||||
if !a.databaseOpened(c) {
|
||||
return
|
||||
}
|
||||
var err error
|
||||
|
||||
fileHeader, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
rowIndex := c.PostForm("rowIndex")
|
||||
comma := c.PostForm("seperator")
|
||||
firstName := c.PostForm("firstName")
|
||||
lastName := c.PostForm("lastName")
|
||||
birthday := c.PostForm("birthday")
|
||||
address := c.PostForm("address")
|
||||
town := c.PostForm("town")
|
||||
zip := c.PostForm("zip")
|
||||
phone := c.PostForm("phone")
|
||||
email := c.PostForm("email")
|
||||
|
||||
rowIndexI, err := strconv.Atoi(rowIndex)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"message": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
file, err := fileHeader.Open()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to open uploaded file"})
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Read first N bytes to detect encoding
|
||||
const sniffSize = 1024 * 10 // 10KB for detection
|
||||
buf := make([]byte, sniffSize)
|
||||
n, err := file.Read(buf)
|
||||
if err != nil && err != io.EOF {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Detect encoding
|
||||
detector := chardet.NewTextDetector()
|
||||
result, err := detector.DetectBest(buf[:n])
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to detect encoding"})
|
||||
return
|
||||
}
|
||||
|
||||
_, err = file.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"message": "Failed to rewind file"})
|
||||
return
|
||||
}
|
||||
|
||||
// Wrap reader based on encoding
|
||||
var reader io.Reader
|
||||
switch result.Charset {
|
||||
case "UTF-8":
|
||||
reader = file
|
||||
case "windows-1252":
|
||||
reader = transform.NewReader(file, charmap.Windows1252.NewDecoder())
|
||||
case "ISO-8859-1":
|
||||
reader = transform.NewReader(file, charmap.ISO8859_1.NewDecoder())
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, gin.H{"message": "Unsupported encoding: " + result.Charset})
|
||||
return
|
||||
}
|
||||
|
||||
csvReader := csv.NewReader(reader)
|
||||
csvReader.Comma = rune(comma[0])
|
||||
|
||||
records, err := csvReader.ReadAll()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to parse CSV"})
|
||||
return
|
||||
}
|
||||
|
||||
var firstNameIndex, lastNameIndex, birthdayIndex, addressIndex, townIndex, zipIndex, phoneIndex, emailIndex int
|
||||
|
||||
var message string
|
||||
for i, row := range records {
|
||||
if i < rowIndexI {
|
||||
continue
|
||||
} else if i == rowIndexI {
|
||||
for c, column := range row {
|
||||
switch column {
|
||||
case firstName:
|
||||
firstNameIndex = c
|
||||
case lastName:
|
||||
lastNameIndex = c
|
||||
case birthday:
|
||||
birthdayIndex = c
|
||||
case address:
|
||||
addressIndex = c
|
||||
case town:
|
||||
townIndex = c
|
||||
case zip:
|
||||
zipIndex = c
|
||||
case phone:
|
||||
phoneIndex = c
|
||||
case email:
|
||||
emailIndex = c
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if row[firstNameIndex] == "" && row[lastNameIndex] == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
err := a.DbHandler.AddNewMember(models.Member{
|
||||
FirstName: row[firstNameIndex],
|
||||
LastName: row[lastNameIndex],
|
||||
Birthday: row[birthdayIndex],
|
||||
Address: row[addressIndex],
|
||||
Town: row[townIndex],
|
||||
Zip: row[zipIndex],
|
||||
Phone: row[phoneIndex],
|
||||
Email: row[emailIndex],
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
message += "Row: " + fmt.Sprint(i) + " " + row[firstNameIndex] + " " + row[lastNameIndex] + " error: " + err.Error() + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
if message != "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"message": message,
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "CSV imported",
|
||||
})
|
||||
}
|
62
crypto/crypto.go
Normal file
62
crypto/crypto.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package crypto
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func Encrypt(text string, key []byte) (string, error) {
|
||||
plaintext := []byte(text)
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
aesGCM, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
nonce := make([]byte, aesGCM.NonceSize())
|
||||
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
ciphertext := aesGCM.Seal(nonce, nonce, plaintext, nil)
|
||||
return base64.StdEncoding.EncodeToString(ciphertext), nil
|
||||
}
|
||||
|
||||
func Decrypt(encrypted string, key []byte) (string, error) {
|
||||
data, err := base64.StdEncoding.DecodeString(encrypted)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
block, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
aesGCM, err := cipher.NewGCM(block)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
nonceSize := aesGCM.NonceSize()
|
||||
if len(data) < nonceSize {
|
||||
return "", fmt.Errorf("ciphertext too short")
|
||||
}
|
||||
|
||||
nonce, ciphertext := data[:nonceSize], data[nonceSize:]
|
||||
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(plaintext), nil
|
||||
}
|
BIN
data/mydatabase.db
Normal file
BIN
data/mydatabase.db
Normal file
Binary file not shown.
52
go.mod
Normal file
52
go.mod
Normal file
@@ -0,0 +1,52 @@
|
||||
module memberDB
|
||||
|
||||
go 1.24.5
|
||||
|
||||
require (
|
||||
github.com/gin-contrib/cors v1.7.6
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d
|
||||
golang.org/x/text v0.27.0
|
||||
modernc.org/sqlite v1.39.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.14.0 // indirect
|
||||
github.com/bytedance/sonic/loader v0.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
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/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/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
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
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
|
||||
golang.org/x/arch v0.20.0 // indirect
|
||||
golang.org/x/crypto v0.40.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
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
modernc.org/libc v1.66.3 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
)
|
131
go.sum
Normal file
131
go.sum
Normal file
@@ -0,0 +1,131 @@
|
||||
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=
|
||||
github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
|
||||
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
|
||||
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/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=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
|
||||
github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
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/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=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
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/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||
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/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/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=
|
||||
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/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=
|
||||
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=
|
||||
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
|
||||
modernc.org/fileutil v1.3.8 h1:qtzNm7ED75pd1C7WgAGcK4edm4fvhtBsEiI/0NQ54YM=
|
||||
modernc.org/fileutil v1.3.8/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ=
|
||||
modernc.org/libc v1.66.3/go.mod h1:XD9zO8kt59cANKvHPXpx7yS2ELPheAey0vjIuZOhOU8=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.39.0 h1:6bwu9Ooim0yVYA7IZn9demiQk/Ejp0BtTjBWFLymSeY=
|
||||
modernc.org/sqlite v1.39.0/go.mod h1:cPTJYSlgg3Sfg046yBShXENNtPrWrDX8bsbAQBzgQ5E=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
534
handlers/database.go
Normal file
534
handlers/database.go
Normal file
@@ -0,0 +1,534 @@
|
||||
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"
|
||||
)
|
||||
|
||||
type DatabaseHandler struct {
|
||||
database *sql.DB
|
||||
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)
|
||||
|
||||
err := os.MkdirAll(dir, os.ModePerm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create directory: %w", 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
18
main.go
Normal file
18
main.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log"
|
||||
"memberDB/api"
|
||||
)
|
||||
|
||||
func main() {
|
||||
port := flag.Int("port", 4040, "server port")
|
||||
url := flag.String("url", "127.0.0.1", "server url")
|
||||
flag.Parse()
|
||||
|
||||
a := api.NewAPI(*url, *port)
|
||||
if err := a.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
6
models/attendees.go
Normal file
6
models/attendees.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package models
|
||||
|
||||
type Attendees struct {
|
||||
FirstName int `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
}
|
15
models/database.go
Normal file
15
models/database.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package models
|
||||
|
||||
type Database struct {
|
||||
Path string `json:"dbPath,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
Create bool `json:"create,omitempty"`
|
||||
}
|
||||
|
||||
func (d *Database) SetDefaultPath() {
|
||||
d.Path = "data/mydatabase.db"
|
||||
}
|
||||
|
||||
func (d *Database) SetDefaultToken() {
|
||||
d.Token = "6576889abd578frdj,ouzt6909-gtrfb"
|
||||
}
|
9
models/event.go
Normal file
9
models/event.go
Normal file
@@ -0,0 +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"`
|
||||
}
|
17
models/member.go
Normal file
17
models/member.go
Normal file
@@ -0,0 +1,17 @@
|
||||
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"`
|
||||
}
|
27
utils/utils.go
Normal file
27
utils/utils.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"net/mail"
|
||||
"time"
|
||||
)
|
||||
|
||||
func IsValidEmail(email string) bool {
|
||||
_, err := mail.ParseAddress(email)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Try multiple accepted date formats
|
||||
var birthdayFormats = []string{
|
||||
"2006-01-02", // ISO: 1999-12-12
|
||||
"02.01.2006", // D.M.Y: 12.12.1999
|
||||
"2.1.2006", // D.M.Y without leading zeros: 1.2.1999
|
||||
}
|
||||
|
||||
func IsValidBirthday(birthday string) bool {
|
||||
for _, layout := range birthdayFormats {
|
||||
if _, err := time.Parse(layout, birthday); err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
Reference in New Issue
Block a user