started working on the backend
This commit is contained in:
30
backend/pkg/db/assignments/assignments.go
Normal file
30
backend/pkg/db/assignments/assignments.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package assignments
|
||||
|
||||
import (
|
||||
"github.com/johannesbuehl/golunteer/backend/pkg/db"
|
||||
)
|
||||
|
||||
type assignments map[string]string
|
||||
|
||||
type assignemntDB struct {
|
||||
TaskName string `db:"taskName"`
|
||||
UserName string `db:"userName"`
|
||||
}
|
||||
|
||||
func Event(eventID int) (assignments, error) {
|
||||
// get the assignments from the database
|
||||
var assignmentRows []assignemntDB
|
||||
|
||||
if err := db.DB.Select(&assignmentRows, "SELECT USERS.name AS userName, TASKS.text AS taskName FROM USER_ASSIGNMENTS JOIN USERS ON USER_ASSIGNMENTS.userName = USERS.name LEFT JOIN TASKS ON USER_ASSIGNMENTS.taskID = TASKS.id WHERE USER_ASSIGNMENTS.eventID = ?", eventID); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
// transform the rows into the returned map
|
||||
eventAssignments := assignments{}
|
||||
|
||||
for _, assignment := range assignmentRows {
|
||||
eventAssignments[assignment.TaskName] = assignment.UserName
|
||||
}
|
||||
|
||||
return eventAssignments, nil
|
||||
}
|
||||
}
|
||||
59
backend/pkg/db/availabilites/availabilities.go
Normal file
59
backend/pkg/db/availabilites/availabilities.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package availabilites
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
cache "github.com/jfarleyx/go-simple-cache"
|
||||
"github.com/johannesbuehl/golunteer/backend/pkg/db"
|
||||
)
|
||||
|
||||
type availabilitesDB struct {
|
||||
Id int `db:"id"`
|
||||
Text string `db:"text"`
|
||||
Disabled bool `db:"disabled"`
|
||||
}
|
||||
|
||||
type Availability struct {
|
||||
Text string
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
var c *cache.Cache
|
||||
|
||||
func Keys() (map[int]Availability, error) {
|
||||
if availabilities, hit := c.Get("availabilites"); !hit {
|
||||
refresh()
|
||||
|
||||
return nil, fmt.Errorf("availabilites not stored cached")
|
||||
} else {
|
||||
return availabilities.(map[int]Availability), nil
|
||||
}
|
||||
}
|
||||
|
||||
func refresh() {
|
||||
// get the availabilitesRaw from the database
|
||||
var availabilitesRaw []availabilitesDB
|
||||
|
||||
if err := db.DB.Select(&availabilitesRaw, "SELECT * FROM AVAILABILITIES"); err == nil {
|
||||
// convert the result in a map
|
||||
availabilites := map[int]Availability{}
|
||||
|
||||
for _, a := range availabilitesRaw {
|
||||
availabilites[a.Id] = Availability{
|
||||
Text: a.Text,
|
||||
Disabled: a.Disabled,
|
||||
}
|
||||
}
|
||||
|
||||
c.Set("availabilites", availabilites)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
c = cache.New(24 * time.Hour)
|
||||
|
||||
c.OnExpired(refresh)
|
||||
|
||||
refresh()
|
||||
}
|
||||
36
backend/pkg/db/availabilites/userAvailabilities.go
Normal file
36
backend/pkg/db/availabilites/userAvailabilities.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package availabilites
|
||||
|
||||
import (
|
||||
"github.com/johannesbuehl/golunteer/backend/pkg/db"
|
||||
"github.com/johannesbuehl/golunteer/backend/pkg/db/users"
|
||||
)
|
||||
|
||||
type eventAvailabilites struct {
|
||||
userName string `db:"userName"`
|
||||
AvailabilityID int `db:"availabilityID"`
|
||||
}
|
||||
|
||||
func Event(eventID int) (map[string]string, error) {
|
||||
// get the availabilites for the event
|
||||
var availabilitesRows []eventAvailabilites
|
||||
|
||||
if err := db.DB.Select(&availabilitesRows, "SELECT (userID, availabilityID) FROM USER_AVAILABILITES WHERE eventID = ?", eventID); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
// transform the result into a map
|
||||
eventAvailabilities := map[string]string{}
|
||||
|
||||
// get the availabilites
|
||||
if availabilitesMap, err := Keys(); err != nil {
|
||||
return nil, err
|
||||
} else if usersMap, err := users.Get(); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
for _, a := range availabilitesRows {
|
||||
eventAvailabilities[usersMap[a.userName].Name] = availabilitesMap[a.AvailabilityID].Text
|
||||
}
|
||||
|
||||
return eventAvailabilities, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
225
backend/pkg/db/db.go
Normal file
225
backend/pkg/db/db.go
Normal file
@@ -0,0 +1,225 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/jmoiron/sqlx"
|
||||
_config "github.com/johannesbuehl/golunteer/backend/pkg/config"
|
||||
_logger "github.com/johannesbuehl/golunteer/backend/pkg/logger"
|
||||
)
|
||||
|
||||
var logger = _logger.Logger
|
||||
var config = _config.Config
|
||||
|
||||
// connection to database
|
||||
var DB *sqlx.DB
|
||||
|
||||
func init() {
|
||||
// setup the database-connection
|
||||
sqlConfig := mysql.Config{
|
||||
AllowNativePasswords: true,
|
||||
Net: "tcp",
|
||||
User: config.Database.User,
|
||||
Passwd: config.Database.Password,
|
||||
Addr: config.Database.Host,
|
||||
DBName: config.Database.Database,
|
||||
}
|
||||
|
||||
// connect to the database
|
||||
DB = sqlx.MustOpen("mysql", sqlConfig.FormatDSN())
|
||||
DB.SetMaxIdleConns(10)
|
||||
DB.SetMaxIdleConns(100)
|
||||
DB.SetConnMaxLifetime(time.Minute)
|
||||
|
||||
}
|
||||
|
||||
// query the database
|
||||
func SelectOld[T any](table string, where string, args ...any) ([]T, error) {
|
||||
// validate columns against struct T
|
||||
tType := reflect.TypeOf(new(T)).Elem()
|
||||
columns := make([]string, tType.NumField())
|
||||
|
||||
validColumns := make(map[string]any)
|
||||
for ii := 0; ii < tType.NumField(); ii++ {
|
||||
field := tType.Field(ii)
|
||||
validColumns[strings.ToLower(field.Name)] = struct{}{}
|
||||
columns[ii] = strings.ToLower(field.Name)
|
||||
}
|
||||
|
||||
for _, col := range columns {
|
||||
if _, ok := validColumns[strings.ToLower(col)]; !ok {
|
||||
return nil, fmt.Errorf("invalid column: %s for struct type %T", col, new(T))
|
||||
}
|
||||
}
|
||||
|
||||
// create the query
|
||||
completeQuery := fmt.Sprintf("SELECT %s FROM %s", strings.Join(columns, ", "), table)
|
||||
|
||||
if where != "" && where != "*" {
|
||||
completeQuery = fmt.Sprintf("%s WHERE %s", completeQuery, where)
|
||||
}
|
||||
|
||||
var rows *sql.Rows
|
||||
var err error
|
||||
|
||||
if len(args) > 0 {
|
||||
DB.Ping()
|
||||
|
||||
rows, err = DB.Query(completeQuery, args...)
|
||||
} else {
|
||||
DB.Ping()
|
||||
|
||||
rows, err = DB.Query(completeQuery)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Error().Msgf("database access failed with error %v", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
results := []T{}
|
||||
|
||||
for rows.Next() {
|
||||
var lineResult T
|
||||
|
||||
scanArgs := make([]any, len(columns))
|
||||
v := reflect.ValueOf(&lineResult).Elem()
|
||||
|
||||
for ii, col := range columns {
|
||||
field := v.FieldByName(col)
|
||||
|
||||
if field.IsValid() && field.CanSet() {
|
||||
scanArgs[ii] = field.Addr().Interface()
|
||||
} else {
|
||||
logger.Warn().Msgf("Field %s not found in struct %T", col, lineResult)
|
||||
scanArgs[ii] = new(any) // save dummy value
|
||||
}
|
||||
}
|
||||
|
||||
// scan the row into the struct
|
||||
if err := rows.Scan(scanArgs...); err != nil {
|
||||
logger.Warn().Msgf("Scan-error: %v", err)
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
results = append(results, lineResult)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
logger.Error().Msgf("rows-error: %v", err)
|
||||
return nil, err
|
||||
} else {
|
||||
return results, nil
|
||||
}
|
||||
}
|
||||
|
||||
// insert data intot the databse
|
||||
func Insert(table string, vals any) error {
|
||||
// extract columns from vals
|
||||
v := reflect.ValueOf(vals)
|
||||
t := v.Type()
|
||||
|
||||
columns := make([]string, t.NumField())
|
||||
values := make([]any, t.NumField())
|
||||
|
||||
for ii := 0; ii < t.NumField(); ii++ {
|
||||
fieldValue := v.Field(ii)
|
||||
|
||||
field := t.Field(ii)
|
||||
|
||||
columns[ii] = strings.ToLower(field.Name)
|
||||
values[ii] = fieldValue.Interface()
|
||||
}
|
||||
|
||||
placeholders := strings.Repeat(("?, "), len(columns))
|
||||
placeholders = strings.TrimSuffix(placeholders, ", ")
|
||||
|
||||
completeQuery := fmt.Sprintf("INSERT INTO %s (%s) VALUES (%s)", table, strings.Join(columns, ", "), placeholders)
|
||||
|
||||
_, err := DB.Exec(completeQuery, values...)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// update data in the database
|
||||
func Update(table string, set, where any) error {
|
||||
setV := reflect.ValueOf(set)
|
||||
setT := setV.Type()
|
||||
|
||||
setColumns := make([]string, setT.NumField())
|
||||
setValues := make([]any, setT.NumField())
|
||||
|
||||
for ii := 0; ii < setT.NumField(); ii++ {
|
||||
fieldValue := setV.Field(ii)
|
||||
|
||||
field := setT.Field(ii)
|
||||
|
||||
setColumns[ii] = strings.ToLower(field.Name) + " = ?"
|
||||
setValues[ii] = fieldValue.Interface()
|
||||
}
|
||||
|
||||
whereV := reflect.ValueOf(where)
|
||||
whereT := whereV.Type()
|
||||
|
||||
whereColumns := make([]string, whereT.NumField())
|
||||
whereValues := make([]any, whereT.NumField())
|
||||
|
||||
for ii := 0; ii < whereT.NumField(); ii++ {
|
||||
fieldValue := whereV.Field(ii)
|
||||
|
||||
// skip empty (zero) values
|
||||
if !fieldValue.IsZero() {
|
||||
field := whereT.Field(ii)
|
||||
|
||||
whereColumns[ii] = strings.ToLower(field.Name) + " = ?"
|
||||
whereValues[ii] = fmt.Sprint(fieldValue.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
sets := strings.Join(setColumns, ", ")
|
||||
wheres := strings.Join(whereColumns, " AND ")
|
||||
|
||||
placeholderValues := append(setValues, whereValues...)
|
||||
|
||||
completeQuery := fmt.Sprintf("UPDATE %s SET %s WHERE %s", table, sets, wheres)
|
||||
|
||||
_, err := DB.Exec(completeQuery, placeholderValues...)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// remove data from the database
|
||||
func Delete(table string, vals any) error {
|
||||
// extract columns from vals
|
||||
v := reflect.ValueOf(vals)
|
||||
t := v.Type()
|
||||
|
||||
columns := make([]string, t.NumField())
|
||||
values := make([]any, t.NumField())
|
||||
|
||||
for ii := 0; ii < t.NumField(); ii++ {
|
||||
fieldValue := v.Field(ii)
|
||||
|
||||
// skip empty (zero) values
|
||||
if !fieldValue.IsZero() {
|
||||
field := t.Field(ii)
|
||||
|
||||
columns[ii] = strings.ToLower(field.Name) + " = ?"
|
||||
values[ii] = fmt.Sprint(fieldValue.Interface())
|
||||
}
|
||||
}
|
||||
|
||||
completeQuery := fmt.Sprintf("DELETE FROM %s WHERE %s", table, strings.Join(columns, ", "))
|
||||
|
||||
_, err := DB.Exec(completeQuery, values...)
|
||||
|
||||
return err
|
||||
}
|
||||
48
backend/pkg/db/events/events.go
Normal file
48
backend/pkg/db/events/events.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"github.com/johannesbuehl/golunteer/backend/pkg/db"
|
||||
"github.com/johannesbuehl/golunteer/backend/pkg/db/assignments"
|
||||
)
|
||||
|
||||
type Event struct {
|
||||
eventDataDB
|
||||
Tasks []string
|
||||
Assignments map[string]string
|
||||
}
|
||||
|
||||
type eventDataDB struct {
|
||||
Id int `db:"id"`
|
||||
Date string `db:"date"`
|
||||
Description string `db:"description"`
|
||||
}
|
||||
|
||||
// transform the database-entry to an Event
|
||||
func (e *eventDataDB) Event() (Event, error) {
|
||||
// get the availabilites associated with the event
|
||||
if assignemnts, err := assignments.Event(e.Id); err != nil {
|
||||
return Event{}, err
|
||||
} else {
|
||||
return Event{
|
||||
eventDataDB: *e,
|
||||
Assignments: assignemnts,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// get all the event ids
|
||||
func All() (map[int]eventDataDB, error) {
|
||||
var dbRows []eventDataDB
|
||||
|
||||
if err := db.DB.Select(&dbRows, "SELECT * FROM EVENTS"); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
eventsMap := map[int]eventDataDB{}
|
||||
|
||||
for _, idRow := range dbRows {
|
||||
eventsMap[idRow.Id] = idRow
|
||||
}
|
||||
|
||||
return eventsMap, nil
|
||||
}
|
||||
}
|
||||
59
backend/pkg/db/tasks/tasks.go
Normal file
59
backend/pkg/db/tasks/tasks.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
cache "github.com/jfarleyx/go-simple-cache"
|
||||
"github.com/johannesbuehl/golunteer/backend/pkg/db"
|
||||
)
|
||||
|
||||
type tasksDB struct {
|
||||
Id int `db:"id"`
|
||||
Text string `db:"text"`
|
||||
Disabled bool `db:"disabled"`
|
||||
}
|
||||
|
||||
type Task struct {
|
||||
Text string
|
||||
Disabled bool
|
||||
}
|
||||
|
||||
var c *cache.Cache
|
||||
|
||||
func Get() (map[int]Task, error) {
|
||||
if tasks, hit := c.Get("tasks"); !hit {
|
||||
refresh()
|
||||
|
||||
return nil, fmt.Errorf("tasks not stored cached")
|
||||
} else {
|
||||
return tasks.(map[int]Task), nil
|
||||
}
|
||||
}
|
||||
|
||||
func refresh() {
|
||||
// get the tasksRaw from the database
|
||||
var tasksRaw []tasksDB
|
||||
|
||||
if err := db.DB.Select(&tasksRaw, "SELECT * FROM TASKS"); err == nil {
|
||||
// convert the result in a map
|
||||
tasks := map[int]Task{}
|
||||
|
||||
for _, a := range tasksRaw {
|
||||
tasks[a.Id] = Task{
|
||||
Text: a.Text,
|
||||
Disabled: a.Disabled,
|
||||
}
|
||||
}
|
||||
|
||||
c.Set("tasks", tasks)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
c = cache.New(24 * time.Hour)
|
||||
|
||||
c.OnExpired(refresh)
|
||||
|
||||
refresh()
|
||||
}
|
||||
52
backend/pkg/db/users/users.go
Normal file
52
backend/pkg/db/users/users.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package users
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
cache "github.com/jfarleyx/go-simple-cache"
|
||||
"github.com/johannesbuehl/golunteer/backend/pkg/db"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Name string `db:"text"`
|
||||
Password []byte `db:"password"`
|
||||
TokenID string `db:"tokenID"`
|
||||
Admin bool `db:"disabled"`
|
||||
}
|
||||
|
||||
var c *cache.Cache
|
||||
|
||||
func Get() (map[string]User, error) {
|
||||
if users, hit := c.Get("users"); !hit {
|
||||
refresh()
|
||||
|
||||
return nil, fmt.Errorf("users not stored cached")
|
||||
} else {
|
||||
return users.(map[string]User), nil
|
||||
}
|
||||
}
|
||||
|
||||
func refresh() {
|
||||
// get the usersRaw from the database
|
||||
var usersRaw []User
|
||||
|
||||
if err := db.DB.Select(&usersRaw, "SELECT * FROM USERS"); err == nil {
|
||||
// convert the result in a map
|
||||
users := map[string]User{}
|
||||
|
||||
for _, user := range usersRaw {
|
||||
users[user.Name] = user
|
||||
}
|
||||
|
||||
c.Set("users", users)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
c = cache.New(24 * time.Hour)
|
||||
|
||||
c.OnExpired(refresh)
|
||||
|
||||
refresh()
|
||||
}
|
||||
Reference in New Issue
Block a user