changed names of database columns

This commit is contained in:
z1glr
2025-01-23 10:12:12 +00:00
parent 7265a4e36a
commit c752bc6c14
36 changed files with 604 additions and 605 deletions

View File

@@ -1,22 +0,0 @@
package assignments
import (
"github.com/johannesbuehl/golunteer/backend/pkg/db"
)
type EventAssignment struct {
TaskID int `db:"taskID" json:"taskID"`
TaskName string `db:"taskName" json:"taskName"`
UserName *string `db:"userName" json:"userName"`
}
func Event(eventID int) ([]EventAssignment, error) {
// get the assignments from the database
var assignmentRows []EventAssignment
if err := db.DB.Select(&assignmentRows, "SELECT USERS.name AS userName, taskID, TASKS.name AS taskName FROM USER_ASSIGNMENTS LEFT 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 {
return assignmentRows, nil
}
}

View File

@@ -5,24 +5,24 @@ import (
) )
type AvailabilityDB struct { type AvailabilityDB struct {
Id int `db:"id" json:"id" validate:"required"` AvailabilityID int `db:"availabilityID" json:"availabilityID" validate:"required"`
Availability `validate:"required"` Availability `validate:"required"`
} }
type Availability struct { type Availability struct {
Name string `db:"name" json:"name" validate:"required"` AvailabilityName string `db:"availabilityName" json:"availabilityName" validate:"required"`
Enabled bool `db:"enabled" json:"enabled" validate:"required"` Enabled bool `db:"enabled" json:"enabled"`
Color string `db:"color" json:"color" validate:"required"` Color string `db:"color" json:"color" validate:"required"`
} }
func Add(a Availability) error { func Add(a Availability) error {
_, err := db.DB.NamedExec("INSERT INTO AVAILABILITIES (name, color, enabled) VALUES (:name, :color, :enabled)", a) _, err := db.DB.NamedExec("INSERT INTO AVAILABILITIES (availabilityName, color, enabled) VALUES (:availabilityName, :color, :enabled)", a)
return err return err
} }
func Update(a AvailabilityDB) error { func Update(a AvailabilityDB) error {
_, err := db.DB.NamedExec("UPDATE AVAILABILITIES SET name = :name, color = :color, enabled = :enabled WHERE id = :id", a) _, err := db.DB.NamedExec("UPDATE AVAILABILITIES SET availabilityName = :availabilityName, color = :color, enabled = :enabled WHERE availabilityID = :availabilityID", a)
return err return err
} }
@@ -46,8 +46,8 @@ func Keys() (map[int]Availability, error) {
availabilities := map[int]Availability{} availabilities := map[int]Availability{}
for _, a := range availabilitiesRaw { for _, a := range availabilitiesRaw {
availabilities[a.Id] = Availability{ availabilities[a.AvailabilityID] = Availability{
Name: a.Name, AvailabilityName: a.AvailabilityName,
Enabled: a.Enabled, Enabled: a.Enabled,
Color: a.Color, Color: a.Color,
} }
@@ -58,7 +58,7 @@ func Keys() (map[int]Availability, error) {
} }
func Delete(id int) error { func Delete(id int) error {
_, err := db.DB.Exec("DELETE FROM AVAILABILITIES WHERE id = $1", id) _, err := db.DB.Exec("DELETE FROM AVAILABILITIES WHERE availabilityID = $1", id)
return err return err
} }

View File

@@ -24,7 +24,7 @@ func Event(eventID int) (map[string]string, error) {
return nil, err return nil, err
} else { } else {
for _, a := range availabilitiesRows { for _, a := range availabilitiesRows {
eventAvailabilities[a.UserName] = availabilitiesMap[a.AvailabilityID].Name eventAvailabilities[a.UserName] = availabilitiesMap[a.AvailabilityID].AvailabilityName
} }
return eventAvailabilities, nil return eventAvailabilities, nil

View File

@@ -31,7 +31,7 @@ func init() {
var admin struct { var admin struct {
Admin bool `db:"admin"` Admin bool `db:"admin"`
} }
if err := DB.QueryRowx("SELECT admin FROM USERS WHERE name = 'admin'").StructScan(&admin); err != nil { if err := DB.QueryRowx("SELECT admin FROM USERS WHERE userName = 'admin'").StructScan(&admin); err != nil {
// if the error isn't because there was no result, it's a real one // if the error isn't because there was no result, it's a real one
if err != sql.ErrNoRows { if err != sql.ErrNoRows {
panic(fmt.Errorf("can't query for the admin-user: %v", err)) panic(fmt.Errorf("can't query for the admin-user: %v", err))

View File

@@ -4,14 +4,30 @@ import (
"slices" "slices"
"github.com/johannesbuehl/golunteer/backend/pkg/db" "github.com/johannesbuehl/golunteer/backend/pkg/db"
"github.com/johannesbuehl/golunteer/backend/pkg/db/assignments"
"github.com/johannesbuehl/golunteer/backend/pkg/db/availabilities" "github.com/johannesbuehl/golunteer/backend/pkg/db/availabilities"
"github.com/johannesbuehl/golunteer/backend/pkg/logger" "github.com/johannesbuehl/golunteer/backend/pkg/logger"
) )
type EventData struct {
EventID int `db:"eventID" json:"eventID" validate:"required"`
Date string `db:"date" json:"date" validate:"required"`
Description string `db:"description" json:"description"`
}
type EventPatch struct {
EventData
Tasks []int `json:"tasks" validate:"required,min=1"`
}
type EventAssignment struct {
TaskID int `db:"taskID" json:"taskID"`
TaskName string `db:"taskName" json:"taskName"`
UserName *string `db:"userName" json:"userName"`
}
type EventWithAssignment struct { type EventWithAssignment struct {
eventDataDB EventData
Tasks []assignments.EventAssignment `json:"tasks"` Tasks []EventAssignment `json:"tasks"`
} }
type EventWithAvailabilities struct { type EventWithAvailabilities struct {
@@ -19,32 +35,32 @@ type EventWithAvailabilities struct {
Availabilities map[string]string `json:"availabilities"` Availabilities map[string]string `json:"availabilities"`
} }
type eventDataDB struct { type EventCreate struct {
ID int `db:"id" json:"id" validate:"required"` Date string `db:"date" json:"date" validate:"required,datetime=2006-01-02T15:04:05.999999999Z"`
Date string `db:"date" json:"date" validate:"required"`
Description string `db:"description" json:"description"` Description string `db:"description" json:"description"`
Tasks []int `json:"tasks" validate:"required,min=1"`
} }
// transform the database-entry to an Event // transform the database-entry to an Event
func (e eventDataDB) Event() (EventWithAssignment, error) { func (e EventData) Event() (EventWithAssignment, error) {
// get the assignments associated with the event // get the assignments associated with the event
if assignemnts, err := assignments.Event(e.ID); err != nil { if assignemnts, err := Assignments(e.EventID); err != nil {
return EventWithAssignment{}, err return EventWithAssignment{}, err
} else { } else {
return EventWithAssignment{ return EventWithAssignment{
eventDataDB: e, EventData: e,
Tasks: assignemnts, Tasks: assignemnts,
}, nil }, nil
} }
} }
func (e eventDataDB) EventWithAvailabilities() (EventWithAvailabilities, error) { func (e EventData) EventWithAvailabilities() (EventWithAvailabilities, error) {
// get the event with assignments // get the event with assignments
if event, err := e.Event(); err != nil { if event, err := e.Event(); err != nil {
return EventWithAvailabilities{}, err return EventWithAvailabilities{}, err
// get the availabilities // get the availabilities
} else if availabilities, err := availabilities.Event(e.ID); err != nil { } else if availabilities, err := availabilities.Event(e.EventID); err != nil {
return EventWithAvailabilities{}, err return EventWithAvailabilities{}, err
} else { } else {
return EventWithAvailabilities{ return EventWithAvailabilities{
@@ -54,12 +70,6 @@ func (e eventDataDB) EventWithAvailabilities() (EventWithAvailabilities, error)
} }
} }
type EventCreate struct {
Date string `db:"date" json:"date" validate:"required,datetime=2006-01-02T15:04:05.999999999Z"`
Description string `db:"description" json:"description"`
Tasks []int `json:"tasks" validate:"required,min=1"`
}
func Create(event EventCreate) error { func Create(event EventCreate) error {
// convert the date to utc // convert the date to utc
if result, err := db.DB.NamedExec("INSERT INTO EVENTS (date, description) VALUES (:date, :description)", event); err != nil { if result, err := db.DB.NamedExec("INSERT INTO EVENTS (date, description) VALUES (:date, :description)", event); err != nil {
@@ -86,7 +96,7 @@ func Create(event EventCreate) error {
// create the assignments // create the assignments
if _, err := db.DB.NamedExec("INSERT INTO USER_ASSIGNMENTS (eventID, taskID) VALUES (:eventID, :taskID)", tasks); err != nil { if _, err := db.DB.NamedExec("INSERT INTO USER_ASSIGNMENTS (eventID, taskID) VALUES (:eventID, :taskID)", tasks); err != nil {
// delete the event again // delete the event again
db.DB.Query("DELETE FROM EVENTS WHERE id = ?", id) db.DB.Query("DELETE FROM EVENTS WHERE eventID = ?", id)
return err return err
} }
@@ -95,25 +105,20 @@ func Create(event EventCreate) error {
return nil return nil
} }
type EventPatch struct {
eventDataDB
Tasks []int `json:"tasks" validate:"required,min=1"`
}
func Update(event EventPatch) error { func Update(event EventPatch) error {
// update the event itself // update the event itself
if _, err := db.DB.NamedExec("UPDATE EVENTS SET description = :description, date = :date WHERE id = :id", event); err != nil { if _, err := db.DB.NamedExec("UPDATE EVENTS SET description = :description, date = :date WHERE eventID = :id", event); err != nil {
return err return err
// get the tasks currently assigned to the event // get the tasks currently assigned to the event
} else { } else {
type TaskID struct { type TaskID struct {
ID int `db:"taskID"` TaskID int `db:"taskID"`
} }
var taskRows []TaskID var taskRows []TaskID
if err := db.DB.Select(&taskRows, "SELECT taskID FROM USER_ASSIGNMENTS WHERE eventID = ?", event.ID); err != nil { if err := db.DB.Select(&taskRows, "SELECT taskID FROM USER_ASSIGNMENTS WHERE eventID = $1", event.EventID); err != nil {
return err return err
} else { } else {
type Task struct { type Task struct {
@@ -125,17 +130,17 @@ func Update(event EventPatch) error {
deleteRows := []Task{} deleteRows := []Task{}
for _, row := range taskRows { for _, row := range taskRows {
if !slices.Contains(event.Tasks, row.ID) { if !slices.Contains(event.Tasks, row.TaskID) {
deleteRows = append(deleteRows, Task{TaskID: row, EventID: event.ID}) deleteRows = append(deleteRows, Task{TaskID: row, EventID: event.EventID})
} }
} }
// extract the rows that need to be created // extract the rows that need to be created
createRows := []Task{} createRows := []Task{}
for _, id := range event.Tasks { for _, taskID := range event.Tasks {
if !slices.Contains(taskRows, TaskID{ID: id}) { if !slices.Contains(taskRows, TaskID{TaskID: taskID}) {
createRows = append(createRows, Task{TaskID: TaskID{ID: id}, EventID: event.ID}) createRows = append(createRows, Task{TaskID: TaskID{TaskID: taskID}, EventID: event.EventID})
} }
} }
@@ -158,8 +163,8 @@ func Update(event EventPatch) error {
} }
} }
func All() ([]eventDataDB, error) { func All() ([]EventData, error) {
var dbRows []eventDataDB var dbRows []EventData
if err := db.DB.Select(&dbRows, "SELECT * FROM EVENTS"); err != nil { if err := db.DB.Select(&dbRows, "SELECT * FROM EVENTS"); err != nil {
return nil, err return nil, err
@@ -177,7 +182,7 @@ func WithAssignments() ([]EventWithAssignment, error) {
for ii, e := range eventsDB { for ii, e := range eventsDB {
if ev, err := e.Event(); err != nil { if ev, err := e.Event(); err != nil {
logger.Logger.Error().Msgf("can't get assignments for event with id = %d: %v", e.ID, err) logger.Logger.Error().Msgf("can't get assignments for event with assignmentID = %d: %v", e.EventID, err)
} else { } else {
events[ii] = ev events[ii] = ev
} }
@@ -196,7 +201,7 @@ func WithAvailabilities() ([]EventWithAvailabilities, error) {
for ii, e := range eventsDB { for ii, e := range eventsDB {
if ev, err := e.EventWithAvailabilities(); err != nil { if ev, err := e.EventWithAvailabilities(); err != nil {
logger.Logger.Error().Msgf("can't get availabilities for event with id = %d: %v", e.ID, err) logger.Logger.Error().Msgf("can't get availabilities for event with eventID = %d: %v", e.EventID, err)
} else { } else {
events[ii] = ev events[ii] = ev
} }
@@ -206,12 +211,22 @@ func WithAvailabilities() ([]EventWithAvailabilities, error) {
} }
} }
func UserPending(userName string) (int, error) { func UserPending(userName string) ([]EventData, error) {
var result []EventData
if err := db.DB.Select(&result, "SELECT eventID, date, description FROM EVENTS WHERE NOT EXISTS (SELECT 1 FROM USER_AVAILABILITIES WHERE USER_AVAILABILITIES.eventID = EVENTS.eventID AND USER_AVAILABILITIES.userName = ?)", userName); err != nil {
return nil, err
} else {
return result, nil
}
}
func UserPendingCount(userName string) (int, error) {
var result struct { var result struct {
Count int `db:"count(*)"` Count int `db:"count(*)"`
} }
if err := db.DB.QueryRowx("SELECT count(*) FROM EVENTS WHERE NOT EXISTS (SELECT 1 FROM USER_AVAILABILITIES WHERE USER_AVAILABILITIES.eventID = EVENTS.id AND USER_AVAILABILITIES.userName = ?)", userName).StructScan(&result); err != nil { if err := db.DB.QueryRowx("SELECT count(*) FROM EVENTS WHERE NOT EXISTS (SELECT 1 FROM USER_AVAILABILITIES WHERE USER_AVAILABILITIES.eventID = EVENTS.eventID AND USER_AVAILABILITIES.userName = ?)", userName).StructScan(&result); err != nil {
return 0, err return 0, err
} else { } else {
return result.Count, nil return result.Count, nil
@@ -219,7 +234,35 @@ func UserPending(userName string) (int, error) {
} }
func Delete(eventId int) error { func Delete(eventId int) error {
_, err := db.DB.Exec("DELETE FROM EVENTS WHERE id = ?", eventId) _, err := db.DB.Exec("DELETE FROM EVENTS WHERE eventID = ?", eventId)
return err return err
} }
func Assignments(eventID int) ([]EventAssignment, error) {
// get the assignments from the database
var assignmentRows []EventAssignment
if err := db.DB.Select(&assignmentRows, "SELECT USERS.userName, TASKS.taskID, TASKS.taskName FROM USER_ASSIGNMENTS LEFT JOIN USERS ON USER_ASSIGNMENTS.userName = USERS.userName LEFT JOIN TASKS ON USER_ASSIGNMENTS.taskID = TASKS.taskID WHERE USER_ASSIGNMENTS.eventID = ?", eventID); err != nil {
return nil, err
} else {
return assignmentRows, nil
}
}
func User(userName string) ([]EventWithAssignment, error) {
// get all assignments of the user
// var events []EventWithAssignment
var events []struct {
EventData
TaskID int `db:"taskID"`
UserName string `db:"userName"`
}
if err := db.DB.Select(&events, "SELECT DISTINCT * FROM USER_ASSIGNMENTS INNER JOIN EVENTS ON USER_ASSIGNMENTS.eventID = EVENTS.eventID WHERE userName = $1", userName); err != nil {
return nil, err
} else {
return nil, nil
}
}

View File

@@ -38,17 +38,17 @@ func setup() {
// create an admin-user // create an admin-user
user := struct { user := struct {
Name string `db:"name"` UserName string `db:"userName"`
Password []byte `db:"password"` Password []byte `db:"password"`
Admin bool `db:"admin"` Admin bool `db:"admin"`
TokenID string `db:"tokenID"` TokenID string `db:"tokenID"`
}{ }{
Name: "admin", UserName: "admin",
Password: passwordHash, Password: passwordHash,
Admin: true, Admin: true,
TokenID: uuid.NewString(), TokenID: uuid.NewString(),
} }
if _, err := DB.NamedExec("INSERT INTO USERS (name, password, tokenID, admin) VALUES (:name, :password, :tokenID, :admin)", &user); err != nil { if _, err := DB.NamedExec("INSERT INTO USERS (userName, password, tokenID, admin) VALUES (:userName, :password, :tokenID, :admin)", &user); err != nil {
panic(fmt.Errorf("can't insert admin-user into the database: %v", err)) panic(fmt.Errorf("can't insert admin-user into the database: %v", err))
} }

View File

@@ -5,12 +5,12 @@ import (
) )
type TaskDB struct { type TaskDB struct {
ID int `json:"id" db:"id" validate:"required"` TaskID int `json:"taskID" db:"taskID" validate:"required"`
Task `valdate:"required" ` Task `valdate:"required" `
} }
type Task struct { type Task struct {
Name string `json:"name" db:"name" validate:"required"` TaskName string `json:"taskName" db:"taskName" validate:"required"`
Enabled bool `json:"enabled" db:"enabled" validate:"required"` Enabled bool `json:"enabled" db:"enabled" validate:"required"`
} }
@@ -33,8 +33,8 @@ func GetMap() (map[int]Task, error) {
tasks := map[int]Task{} tasks := map[int]Task{}
for _, a := range tasksRaw { for _, a := range tasksRaw {
tasks[a.ID] = Task{ tasks[a.TaskID] = Task{
Name: a.Name, TaskName: a.TaskName,
Enabled: a.Enabled, Enabled: a.Enabled,
} }
} }
@@ -44,19 +44,19 @@ func GetMap() (map[int]Task, error) {
} }
func Add(t Task) error { func Add(t Task) error {
_, err := db.DB.NamedExec("INSERT INTO TASKS (name, enabled) VALUES (:name, :enabled)", &t) _, err := db.DB.NamedExec("INSERT INTO TASKS (taskName, enabled) VALUES (:taskName, :enabled)", &t)
return err return err
} }
func Update(t TaskDB) error { func Update(t TaskDB) error {
_, err := db.DB.NamedExec("UPDATE TASKS set name = :name, enabled = :enabled WHERE id = :id", &t) _, err := db.DB.NamedExec("UPDATE TASKS set taskName = :taskName, enabled = :enabled WHERE taskID = :taskID", &t)
return err return err
} }
func Delete(i int) error { func Delete(i int) error {
_, err := db.DB.Exec("DELETE FROM TASKS WHERE id = $1", i) _, err := db.DB.Exec("DELETE FROM TASKS WHERE taskID = $1", i)
return err return err
} }

View File

@@ -7,7 +7,7 @@ import (
) )
type User struct { type User struct {
Name string `db:"name" json:"userName"` UserName string `db:"userName" json:"userName"`
Admin bool `db:"admin" json:"admin"` Admin bool `db:"admin" json:"admin"`
} }
@@ -25,7 +25,7 @@ func Get() ([]User, error) {
// get the users from the database // get the users from the database
var users []User var users []User
if err := db.DB.Select(&users, "SELECT name, admin FROM USERS"); err != nil { if err := db.DB.Select(&users, "SELECT userName, admin FROM USERS"); err != nil {
return nil, err return nil, err
} else { } else {
return users, nil return users, nil
@@ -37,7 +37,7 @@ func TokenID(userName string) (string, error) {
TokenID string `db:"tokenID"` TokenID string `db:"tokenID"`
} }
err := db.DB.Get(&dbResult, "SELECT tokenID FROM USERS WHERE name = ?", userName) err := db.DB.Get(&dbResult, "SELECT tokenID FROM USERS WHERE userName = ?", userName)
return dbResult.TokenID, err return dbResult.TokenID, err
} }
@@ -63,7 +63,7 @@ func Add(user UserAdd) error {
TokenID: uuid.NewString(), TokenID: uuid.NewString(),
} }
_, err := db.DB.NamedExec("INSERT INTO USERS (name, password, admin, tokenID) VALUES (:userName, :password, :admin, :tokenID)", insertUser) _, err := db.DB.NamedExec("INSERT INTO USERS (userName, password, admin, tokenID) VALUES (:userName, :password, :admin, :tokenID)", insertUser)
return err return err
} }
@@ -93,19 +93,19 @@ func ChangePassword(user UserChangePassword) (string, error) {
} }
func ChangeName(userName, newName string) error { func ChangeName(userName, newName string) error {
_, err := db.DB.Exec("UPDATE USERS SET name = ? WHERE name = ?", newName, userName) _, err := db.DB.Exec("UPDATE USERS SET userName = ? WHERE userName = ?", newName, userName)
return err return err
} }
func SetAdmin(userName string, admin bool) error { func SetAdmin(userName string, admin bool) error {
_, err := db.DB.Exec("UPDATE USERS SET admin = ? WHERE name = ?", admin, userName) _, err := db.DB.Exec("UPDATE USERS SET admin = ? WHERE userName = ?", admin, userName)
return err return err
} }
func Delete(userName string) error { func Delete(userName string) error {
_, err := db.DB.Exec("DELETE FROM USERS WHERE name = $1", userName) _, err := db.DB.Exec("DELETE FROM USERS WHERE userName = $1", userName)
return err return err
} }

View File

@@ -5,130 +5,93 @@ import (
"github.com/johannesbuehl/golunteer/backend/pkg/db/availabilities" "github.com/johannesbuehl/golunteer/backend/pkg/db/availabilities"
) )
func getAvailabilities(args HandlerArgs) responseMessage { func (a *Handler) getAvailabilities() {
response := responseMessage{}
// get all the availabilites from the database // get all the availabilites from the database
if avails, err := availabilities.Slice(); err != nil { if avails, err := availabilities.Slice(); err != nil {
response.Status = fiber.StatusInternalServerError a.Status = fiber.StatusInternalServerError
logger.Error().Msgf("can't get availabilites: %v", err) logger.Error().Msgf("can't get availabilites: %v", err)
return response
} else { } else {
response.Data = avails a.Data = avails
return response
} }
} }
func postAvailability(args HandlerArgs) responseMessage { func (a *Handler) postAvailability() {
response := responseMessage{}
// check admin // check admin
if !args.User.Admin { if !a.Admin {
response.Status = fiber.StatusUnauthorized a.Status = fiber.StatusUnauthorized
logger.Warn().Msg("user is no admin") logger.Warn().Msg("user is no admin")
return response
// parse the body // parse the body
} else { } else {
var body availabilities.Availability var body availabilities.Availability
if err := args.C.BodyParser(&body); err != nil { if err := a.C.BodyParser(&body); err != nil {
response.Status = fiber.StatusBadRequest a.Status = fiber.StatusBadRequest
logger.Log().Msgf("can't parse body: %v", err) logger.Log().Msgf("can't parse body: %v", err)
return response
// validate the body // validate the body
} else if err := validate.Struct(&response); err != nil { } else if err := validate.Struct(&body); err != nil {
response.Status = fiber.StatusBadRequest a.Status = fiber.StatusBadRequest
logger.Log().Msgf("invalid body: %v", err) logger.Log().Msgf("invalid body: %v", err)
return response
} else if err := availabilities.Add(body); err != nil { } else if err := availabilities.Add(body); err != nil {
response.Status = fiber.StatusInternalServerError a.Status = fiber.StatusInternalServerError
logger.Error().Msgf("can't add availability: %v", err) logger.Error().Msgf("can't add availability: %v", err)
return response
} else {
return response
} }
} }
} }
func patchAvailabilitiy(args HandlerArgs) responseMessage { func (a *Handler) patchAvailabilitiy() {
response := responseMessage{}
// check admin // check admin
if !args.User.Admin { if !a.Admin {
response.Status = fiber.StatusUnauthorized a.Status = fiber.StatusUnauthorized
logger.Warn().Msg("user is no admin") logger.Warn().Msg("user is no admin")
return response
// parse the body // parse the body
} else { } else {
var body availabilities.AvailabilityDB var body availabilities.AvailabilityDB
if err := args.C.BodyParser(&body); err != nil { if err := a.C.BodyParser(&body); err != nil {
response.Status = fiber.StatusBadRequest a.Status = fiber.StatusBadRequest
logger.Log().Msgf("can't parse body: %v", err) logger.Log().Msgf("can't parse body: %v", err)
return response
// validate the body // validate the body
} else if err := validate.Struct(&response); err != nil { } else if err := validate.Struct(&body); err != nil {
response.Status = fiber.StatusBadRequest a.Status = fiber.StatusBadRequest
logger.Log().Msgf("invalid body: %v", err) logger.Log().Msgf("invalid body: %v", err)
return response
} else if err := availabilities.Update(body); err != nil { } else if err := availabilities.Update(body); err != nil {
response.Status = fiber.StatusInternalServerError a.Status = fiber.StatusInternalServerError
logger.Error().Msgf("can't update availability: %v", err) logger.Error().Msgf("can't update availability: %v", err)
return response
} else {
return response
} }
} }
} }
func deleteAvailability(args HandlerArgs) responseMessage { func (a *Handler) deleteAvailability() {
// check admin // check admin
if !args.User.Admin { if !a.Admin {
logger.Warn().Msg("availability-deletion failed: user is no admin") logger.Warn().Msg("availability-deletion failed: user is no admin")
return responseMessage{ a.Status = fiber.StatusUnauthorized
Status: fiber.StatusUnauthorized,
}
// parse the query // parse the query
} else if taskID := args.C.QueryInt("id", -1); taskID == -1 { } else if taskID := a.C.QueryInt("availabilityID", -1); taskID == -1 {
logger.Log().Msg("availability-deletion failed: invalid query: doesn't include \"id\"") logger.Log().Msg("availability-deletion failed: invalid query: doesn't include \"availabilityID\"")
return responseMessage{ a.Status = fiber.StatusBadRequest
Status: fiber.StatusBadRequest,
}
// delete the task from the database // delete the task from the database
} else if err := availabilities.Delete(taskID); err != nil { } else if err := availabilities.Delete(taskID); err != nil {
logger.Error().Msgf("availability-deletion failed: can't delete task from database: %v", err) logger.Error().Msgf("availability-deletion failed: can't delete task from database: %v", err)
return responseMessage{ a.Status = fiber.StatusInternalServerError
Status: fiber.StatusInternalServerError,
}
} else {
return responseMessage{}
} }
} }

View File

@@ -5,144 +5,138 @@ import (
"github.com/johannesbuehl/golunteer/backend/pkg/db/events" "github.com/johannesbuehl/golunteer/backend/pkg/db/events"
) )
func postEvent(args HandlerArgs) responseMessage { func (a *Handler) postEvent() {
response := responseMessage{}
// check admin // check admin
if !args.User.Admin { if !a.Admin {
response.Status = fiber.StatusForbidden a.Status = fiber.StatusForbidden
} else { } else {
// write the event // write the event
var body events.EventCreate var body events.EventCreate
// try to parse the body // try to parse the body
if err := args.C.BodyParser(&body); err != nil { if err := a.C.BodyParser(&body); err != nil {
response.Status = fiber.StatusBadRequest a.Status = fiber.StatusBadRequest
logger.Log().Msgf("can't parse body: %v", err) logger.Log().Msgf("can't parse body: %v", err)
// validate the parsed body // validate the parsed body
} else if err := validate.Struct(body); err != nil { } else if err := validate.Struct(body); err != nil {
response.Status = fiber.StatusBadRequest a.Status = fiber.StatusBadRequest
logger.Log().Msgf("invalid body: %v", err) logger.Log().Msgf("invalid body: %v", err)
// create the event // create the event
} else if err := events.Create(body); err != nil { } else if err := events.Create(body); err != nil {
response.Status = fiber.StatusInternalServerError a.Status = fiber.StatusInternalServerError
logger.Error().Msgf("can't create event: %v", err) logger.Error().Msgf("can't create event: %v", err)
} }
} }
return response
} }
func patchEvent(args HandlerArgs) responseMessage { func (a *Handler) patchEvent() {
response := responseMessage{}
// check admin // check admin
if !args.User.Admin { if !a.Admin {
response.Status = fiber.StatusForbidden a.Status = fiber.StatusForbidden
} else { } else {
// parse the body // parse the body
var body events.EventPatch var body events.EventPatch
if err := args.C.BodyParser(&body); err != nil { if err := a.C.BodyParser(&body); err != nil {
response.Status = fiber.StatusBadRequest a.Status = fiber.StatusBadRequest
logger.Log().Msgf("can't parse body: %v", err) logger.Log().Msgf("can't parse body: %v", err)
// validate the body // validate the body
} else if err := validate.Struct(body); err != nil { } else if err := validate.Struct(body); err != nil {
response.Status = fiber.StatusBadRequest a.Status = fiber.StatusBadRequest
logger.Log().Msgf("ivnalid body: %v", err) logger.Log().Msgf("ivnalid body: %v", err)
// update the event // update the event
} else if err := events.Update(body); err != nil { } else if err := events.Update(body); err != nil {
response.Status = fiber.StatusInternalServerError a.Status = fiber.StatusInternalServerError
logger.Error().Msgf("updating the event failed: %v", err) logger.Error().Msgf("updating the event failed: %v", err)
} }
} }
return response
} }
func getEventsAssignments(args HandlerArgs) responseMessage { func (a *Handler) getEventsAssignments() {
response := responseMessage{}
if events, err := events.WithAssignments(); err != nil { if events, err := events.WithAssignments(); err != nil {
response.Status = fiber.StatusInternalServerError a.Status = fiber.StatusInternalServerError
logger.Error().Msgf("can't retrieve events with assignments: %v", err) logger.Error().Msgf("can't retrieve events with assignments: %v", err)
} else { } else {
response.Data = events a.Data = events
}
} }
return response func (a *Handler) getEventsAvailabilities() {
}
func getEventsAvailabilities(args HandlerArgs) responseMessage {
response := responseMessage{}
// check for admin // check for admin
if !args.User.Admin { if !a.Admin {
response.Status = fiber.StatusForbidden a.Status = fiber.StatusForbidden
} else { } else {
if events, err := events.WithAvailabilities(); err != nil { if events, err := events.WithAvailabilities(); err != nil {
response.Status = fiber.StatusInternalServerError a.Status = fiber.StatusInternalServerError
logger.Error().Msgf("can't retrieve events with availabilities: %v", err) logger.Error().Msgf("can't retrieve events with availabilities: %v", err)
} else { } else {
response.Data = events a.Data = events
}
} }
} }
return response func (a *Handler) getEventsUserPending() {
} if events, err := events.UserPending(a.UserName); err != nil {
a.Status = fiber.StatusInternalServerError
func getEventsUserPending(args HandlerArgs) responseMessage { logger.Warn().Msgf("can't query database for users %q pending events: %v", a.UserName, err)
response := responseMessage{}
if count, err := events.UserPending(args.User.UserName); err != nil {
response.Status = fiber.StatusInternalServerError
logger.Warn().Msgf("can't query database for users %q pending events: %v", args.User.UserName, err)
} else { } else {
response.Data = count a.Data = events
}
} }
return response func (a *Handler) getEventsUserPendingCount() {
if count, err := events.UserPendingCount(a.UserName); err != nil {
a.Status = fiber.StatusInternalServerError
logger.Warn().Msgf("can't query database for users %q pending events: %v", a.UserName, err)
} else {
a.Data = count
}
} }
func deleteEvent(args HandlerArgs) responseMessage { func (a *Handler) getEventsUserAssigned() {
// retrieve the events from the database
if events, err := events.User(a.UserName); err != nil {
a.Status = fiber.StatusBadRequest
logger.Log().Msgf("retrieval of user-assigned-events failed: %v", err)
} else {
a.Data = events
}
}
func (a *Handler) deleteEvent() {
// check for admin // check for admin
if !args.User.Admin { if !a.Admin {
logger.Warn().Msg("event-delete failed: user is no admin") logger.Warn().Msg("event-delete failed: user is no admin")
return responseMessage{ a.Status = fiber.StatusForbidden
Status: fiber.StatusForbidden,
}
// -1 can't be valid // -1 can't be valid
} else if eventId := args.C.QueryInt("id", -1); eventId == -1 { } else if eventId := a.C.QueryInt("eventID", -1); eventId == -1 {
logger.Log().Msgf("event-delete failed: \"id\" is missing in query") logger.Log().Msgf("event-delete failed: \"eventID\" is missing in query")
return responseMessage{ a.Status = fiber.StatusBadRequest
Status: fiber.StatusBadRequest,
}
} else if err := events.Delete(eventId); err != nil { } else if err := events.Delete(eventId); err != nil {
logger.Error().Msgf("event-delete failed: can't delete from database: %v", err) logger.Error().Msgf("event-delete failed: can't delete from database: %v", err)
return responseMessage{ a.Status = fiber.StatusInternalServerError
Status: fiber.StatusInternalServerError,
}
} else { } else {
logger.Log().Msgf("deleted event with id %d", eventId) logger.Log().Msgf("deleted event with eventID %d", eventId)
return responseMessage{}
} }
} }

View File

@@ -8,33 +8,32 @@ import (
// handle welcome-messages from clients // handle welcome-messages from clients
func handleWelcome(c *fiber.Ctx) error { func handleWelcome(c *fiber.Ctx) error {
args := Handler{C: c}
logger.Debug().Msgf("HTTP %s request: %q", c.Method(), c.OriginalURL()) logger.Debug().Msgf("HTTP %s request: %q", c.Method(), c.OriginalURL())
response := responseMessage{} args.Data = UserChecked{
response.Data = UserChecked{
Admin: false, Admin: false,
} }
args := HandlerArgs{C: c}
if loggedIn, err := args.checkUser(); err != nil { if loggedIn, err := args.checkUser(); err != nil {
response.Status = fiber.StatusInternalServerError args.Status = fiber.StatusInternalServerError
logger.Warn().Msgf("can't check user: %v", err) logger.Warn().Msgf("can't check user: %v", err)
} else if !loggedIn { } else if !loggedIn {
response.Status = fiber.StatusUnauthorized args.Status = fiber.StatusUnauthorized
logger.Debug().Msgf("user not authorized") logger.Debug().Msgf("user not authorized")
} else { } else {
response.Data = UserChecked{ args.Data = UserChecked{
UserName: args.User.UserName, UserName: args.UserName,
Admin: args.User.Admin, Admin: args.Admin,
} }
logger.Debug().Msgf("welcomed user %q", args.User.UserName) logger.Debug().Msgf("welcomed user %q", args.UserName)
} }
return response.send(c) return args.send(c)
} }
const messageWrongLogin = "Unkown user or wrong password" const messageWrongLogin = "Unkown user or wrong password"
@@ -42,7 +41,7 @@ const messageWrongLogin = "Unkown user or wrong password"
func handleLogin(c *fiber.Ctx) error { func handleLogin(c *fiber.Ctx) error {
logger.Debug().Msgf("HTTP %s request: %q", c.Method(), c.OriginalURL()) logger.Debug().Msgf("HTTP %s request: %q", c.Method(), c.OriginalURL())
args := HandlerArgs{C: c} args := Handler{C: c}
// extract username and password from the request // extract username and password from the request
requestBody := struct { requestBody := struct {
@@ -50,12 +49,10 @@ func handleLogin(c *fiber.Ctx) error {
Password string `json:"password" validate:"required"` Password string `json:"password" validate:"required"`
}{} }{}
var response responseMessage
if err := args.C.BodyParser(&requestBody); err != nil { if err := args.C.BodyParser(&requestBody); err != nil {
logger.Debug().Msgf("can't parse login-body: %v", err) logger.Debug().Msgf("can't parse login-body: %v", err)
response.Status = fiber.StatusBadRequest args.Status = fiber.StatusBadRequest
// validate the body // validate the body
} else if err := validate.Struct(requestBody); err != nil { } else if err := validate.Struct(requestBody); err != nil {
@@ -63,29 +60,29 @@ func handleLogin(c *fiber.Ctx) error {
} else { } else {
// query the database for the user // query the database for the user
var result userDB var result userDB
if err := db.DB.QueryRowx("SELECT password, admin, tokenID FROM USERS WHERE name = ?", requestBody.Username).StructScan(&result); err != nil { if err := db.DB.QueryRowx("SELECT password, admin, tokenID FROM USERS WHERE userName = ?", requestBody.Username).StructScan(&result); err != nil {
response.Status = fiber.StatusForbidden args.Status = fiber.StatusForbidden
response.Message = messageWrongLogin args.Message = messageWrongLogin
logger.Info().Msgf("can't get user with name = %q from database", requestBody.Username) logger.Info().Msgf("can't get user with userName = %q from database", requestBody.Username)
} else { } else {
// hash the password // hash the password
if bcrypt.CompareHashAndPassword(result.Password, []byte(requestBody.Password)) != nil { if bcrypt.CompareHashAndPassword(result.Password, []byte(requestBody.Password)) != nil {
response.Status = fiber.StatusForbidden args.Status = fiber.StatusForbidden
logger.Info().Msgf("login denied: wrong password for user with name = %q", requestBody.Username) logger.Info().Msgf("login denied: wrong password for user with userName = %q", requestBody.Username)
} else { } else {
// password is correct -> generate the JWT // password is correct -> generate the JWT
if jwt, err := config.SignJWT(JWTPayload{ if jwt, err := config.SignJWT(JWTPayload{
UserName: requestBody.Username, UserName: requestBody.Username,
TokenID: result.TokenID, TokenID: result.TokenID,
}); err != nil { }); err != nil {
response.Status = fiber.StatusInternalServerError args.Status = fiber.StatusInternalServerError
logger.Error().Msgf("can't create JWT: %v", err) logger.Error().Msgf("can't create JWT: %v", err)
} else { } else {
args.setSessionCookie(&jwt) args.setSessionCookie(&jwt)
response.Data = UserChecked{ args.Data = UserChecked{
UserName: requestBody.Username, UserName: requestBody.Username,
Admin: true, Admin: true,
} }
@@ -96,18 +93,18 @@ func handleLogin(c *fiber.Ctx) error {
} }
} }
return response.send(args.C) return args.send(args.C)
} }
// handles logout-requests // handles logout-requests
func handleLogout(c *fiber.Ctx) error { func handleLogout(c *fiber.Ctx) error {
logger.Debug().Msgf("HTTP %s request: %q", c.Method(), c.OriginalURL()) logger.Debug().Msgf("HTTP %s request: %q", c.Method(), c.OriginalURL())
args := HandlerArgs{ args := Handler{
C: c, C: c,
} }
args.removeSessionCookie() args.removeSessionCookie()
return responseMessage{}.send(c) return args.send(c)
} }

View File

@@ -27,32 +27,33 @@ type responseMessage struct {
} }
// answer the client request with the response-message // answer the client request with the response-message
func (result responseMessage) send(c *fiber.Ctx) error { func (a *Handler) send(c *fiber.Ctx) error {
// if the status-code is in the error-region, return an error // if the status-code is in the error-region, return an error
if result.Status >= 400 { if a.Status >= 400 {
// if available, include the message // if available, include the message
if result.Message != "" { if a.Message != "" {
return fiber.NewError(result.Status, result.Message) return fiber.NewError(a.Status, a.Message)
} else { } else {
return fiber.NewError(result.Status) return fiber.NewError(a.Status)
} }
} else { } else {
// if there is data, send it as JSON // if there is data, send it as JSON
if result.Data != nil { if a.Data != nil {
c.JSON(result.Data) c.JSON(a.Data)
// if there is a message, send it instead // if there is a message, send it instead
} else if result.Message != "" { } else if a.Message != "" {
c.SendString(result.Message) c.SendString(a.Message)
} }
return c.SendStatus(result.Status) return c.SendStatus(a.Status)
} }
} }
type HandlerArgs struct { type Handler struct {
C *fiber.Ctx C *fiber.Ctx
User UserChecked UserChecked
responseMessage
} }
func init() { func init() {
@@ -74,35 +75,45 @@ func init() {
} }
// map with the individual registered endpoints // map with the individual registered endpoints
endpoints := map[string]map[string]func(HandlerArgs) responseMessage{ endpoints := map[string]map[string]func(*Handler){
"GET": { "GET": {
"events/assignments": getEventsAssignments, // all events with the task-assignments
"events/availabilities": getEventsAvailabilities, "events/assignments": (*Handler).getEventsAssignments,
"events/user/pending": getEventsUserPending,
"tasks": getTasks, // all events with the availabilities of the individual users
"users": getUsers, "events/availabilities": (*Handler).getEventsAvailabilities,
"availabilities": getAvailabilities,
// events the user has to enter his availability for
"events/user/pending": (*Handler).getEventsUserPending,
// number of events the user has to enter his availability for
"events/user/pending/count": (*Handler).getEventsUserPendingCount,
"events/user/assigned": (*Handler).getEventsUserAssigned,
"tasks": (*Handler).getTasks, // all available tasks
"users": (*Handler).getUsers, // all users
"availabilities": (*Handler).getAvailabilities, // all available availabilities
}, },
"POST": { "POST": {
"events": postEvent, "events": (*Handler).postEvent, // create an event
"users": postUser, "users": (*Handler).postUser, // add an user
"availabilities": postAvailability, "availabilities": (*Handler).postAvailability, // add an availability
"tasks": postTask, "tasks": (*Handler).postTask, // add a task
}, },
"PATCH": { "PATCH": {
"users": patchUser, "users": (*Handler).patchUser, // modify an user
"events": patchEvent, "events": (*Handler).patchEvent, // modify an event
"availabilities": patchAvailabilitiy, "availabilities": (*Handler).patchAvailabilitiy, // modify an availability
"tasks": patchTask, "tasks": (*Handler).patchTask, // modify a task
}, },
"PUT": { "PUT": {
"users/password": putPassword, "users/password": (*Handler).putPassword, // change the password
}, },
"DELETE": { "DELETE": {
"event": deleteEvent, "event": (*Handler).deleteEvent, // remove an event
"tasks": deleteTask, "tasks": (*Handler).deleteTask, // remove a task
"availabilities": deleteAvailability, "availabilities": (*Handler).deleteAvailability, // remove an availability
"users": deleteUser, "users": (*Handler).deleteUser, // remove an user
}, },
} }
@@ -117,28 +128,23 @@ func init() {
handleMethods[method]("/api/"+address, func(c *fiber.Ctx) error { handleMethods[method]("/api/"+address, func(c *fiber.Ctx) error {
logger.Debug().Msgf("HTTP %s request: %q", c.Method(), c.OriginalURL()) logger.Debug().Msgf("HTTP %s request: %q", c.Method(), c.OriginalURL())
var response responseMessage args := Handler{
args := HandlerArgs{
C: c, C: c,
} }
if loggedIn, err := args.checkUser(); err != nil { if loggedIn, err := args.checkUser(); err != nil {
response = responseMessage{ args.Status = fiber.StatusBadRequest
Status: fiber.StatusBadRequest,
}
logger.Error().Msgf("can't check user: %v", err) logger.Error().Msgf("can't check user: %v", err)
} else if !loggedIn { } else if !loggedIn {
response = responseMessage{ args.Status = fiber.StatusUnauthorized
Status: fiber.StatusUnauthorized,
}
logger.Log().Msgf("user not authorized") logger.Log().Msgf("user not authorized")
} else {
response = handler(args)
} }
return response.send(c) handler(&args)
return args.send(c)
}) })
} }
} }
@@ -151,7 +157,7 @@ func Listen() {
fmt.Println(err) fmt.Println(err)
} }
func (args HandlerArgs) setSessionCookie(jwt *string) { func (args Handler) setSessionCookie(jwt *string) {
var value string var value string
if jwt == nil { if jwt == nil {
@@ -170,7 +176,7 @@ func (args HandlerArgs) setSessionCookie(jwt *string) {
} }
// removes the session-coockie from a request // removes the session-coockie from a request
func (args HandlerArgs) removeSessionCookie() { func (args Handler) removeSessionCookie() {
args.C.Cookie(&fiber.Cookie{ args.C.Cookie(&fiber.Cookie{
Name: "session", Name: "session",
Value: "", Value: "",
@@ -233,7 +239,7 @@ type UserChecked struct {
} }
// checks wether the request is from a valid user // checks wether the request is from a valid user
func (args *HandlerArgs) checkUser() (bool, error) { func (args *Handler) checkUser() (bool, error) {
userName, tokenID, err := extractJWT(args.C) userName, tokenID, err := extractJWT(args.C)
if err != nil { if err != nil {
@@ -246,7 +252,7 @@ func (args *HandlerArgs) checkUser() (bool, error) {
} }
// retrieve the user from the database // retrieve the user from the database
if err := db.DB.Get(&dbResult, "SELECT tokenID, admin FROM USERS WHERE name = ?", userName); err != nil { if err := db.DB.Get(&dbResult, "SELECT tokenID, admin FROM USERS WHERE userName = ?", userName); err != nil {
return false, err return false, err
// if the tokenID is valid, the user is authorized // if the tokenID is valid, the user is authorized
@@ -256,11 +262,9 @@ func (args *HandlerArgs) checkUser() (bool, error) {
// reset the expiration of the cookie // reset the expiration of the cookie
args.setSessionCookie(nil) args.setSessionCookie(nil)
args.User = UserChecked{ args.UserName = userName
UserName: userName, args.Admin = dbResult.Admin
Admin: dbResult.Admin,
} }
return true, nil return true, nil
} }
}

View File

@@ -5,125 +5,93 @@ import (
"github.com/johannesbuehl/golunteer/backend/pkg/db/tasks" "github.com/johannesbuehl/golunteer/backend/pkg/db/tasks"
) )
func getTasks(args HandlerArgs) responseMessage { func (a *Handler) getTasks() {
if taskSlice, err := tasks.GetSlice(); err != nil { if taskSlice, err := tasks.GetSlice(); err != nil {
logger.Error().Msgf("can't get tasks: %v", err) logger.Error().Msgf("can't get tasks: %v", err)
return responseMessage{ a.Status = fiber.StatusInternalServerError
Status: fiber.StatusInternalServerError,
}
} else { } else {
return responseMessage{ a.Data = taskSlice
Data: taskSlice,
}
} }
} }
func postTask(args HandlerArgs) responseMessage { func (a *Handler) postTask() {
// check admin // check admin
if !args.User.Admin { if !a.Admin {
logger.Log().Msgf("user is not admin") logger.Log().Msgf("user is not admin")
return responseMessage{ a.Status = fiber.StatusUnauthorized
Status: fiber.StatusUnauthorized,
}
} else { } else {
// parse the body // parse the body
var task tasks.Task var task tasks.Task
if err := args.C.BodyParser(&task); err != nil { if err := a.C.BodyParser(&task); err != nil {
logger.Log().Msgf("can't parse body: %v", err) logger.Log().Msgf("can't parse body: %v", err)
return responseMessage{ a.Status = fiber.StatusBadRequest
Status: fiber.StatusBadRequest,
}
// validate the body // validate the body
} else if err := validate.Struct(&task); err != nil { } else if err := validate.Struct(&task); err != nil {
logger.Log().Msgf("invalid body: %v", err) logger.Log().Msgf("invalid body: %v", err)
return responseMessage{ a.Status = fiber.StatusBadRequest
Status: fiber.StatusBadRequest,
}
// insert the task into the database // insert the task into the database
} else if err := tasks.Add(task); err != nil { } else if err := tasks.Add(task); err != nil {
logger.Error().Msgf("can't add task: %v", err) logger.Error().Msgf("can't add task: %v", err)
return responseMessage{ a.Status = fiber.StatusInternalServerError
Status: fiber.StatusInternalServerError,
}
} else {
return responseMessage{}
} }
} }
} }
func patchTask(args HandlerArgs) responseMessage { func (a *Handler) patchTask() {
// check admin // check admin
if !args.User.Admin { if !a.Admin {
logger.Log().Msgf("user is not admin") logger.Log().Msgf("user is not admin")
return responseMessage{ a.Status = fiber.StatusUnauthorized
Status: fiber.StatusUnauthorized,
}
} else { } else {
// parse the body // parse the body
var task tasks.TaskDB var task tasks.TaskDB
if err := args.C.BodyParser(&task); err != nil { if err := a.C.BodyParser(&task); err != nil {
logger.Log().Msgf("can't parse body: %v", err) logger.Log().Msgf("can't parse body: %v", err)
return responseMessage{ a.Status = fiber.StatusBadRequest
Status: fiber.StatusBadRequest,
}
// validate the body // validate the body
} else if err := validate.Struct(&task); err != nil { } else if err := validate.Struct(&task); err != nil {
logger.Log().Msgf("invalid body: %v", err) logger.Log().Msgf("invalid body: %v", err)
return responseMessage{ a.Status = fiber.StatusBadRequest
Status: fiber.StatusBadRequest,
}
// insert the task into the database // insert the task into the database
} else if err := tasks.Update(task); err != nil { } else if err := tasks.Update(task); err != nil {
logger.Error().Msgf("can't update task: %v", err) logger.Error().Msgf("can't update task: %v", err)
return responseMessage{ a.Status = fiber.StatusInternalServerError
Status: fiber.StatusInternalServerError,
}
} else {
return responseMessage{}
} }
} }
} }
func deleteTask(args HandlerArgs) responseMessage { func (a *Handler) deleteTask() {
// check admin // check admin
if !args.User.Admin { if !a.Admin {
logger.Warn().Msg("task-deletion failed: user is no admin") logger.Warn().Msg("task-deletion failed: user is no admin")
return responseMessage{ a.Status = fiber.StatusUnauthorized
Status: fiber.StatusUnauthorized,
}
// parse the query // parse the query
} else if taskID := args.C.QueryInt("id", -1); taskID == -1 { } else if taskID := a.C.QueryInt("taskID", -1); taskID == -1 {
logger.Log().Msg("task-deletion failed: invalid query: doesn't include \"id\"") logger.Log().Msg("task-deletion failed: invalid query: doesn't include \"taskID\"")
return responseMessage{ a.Status = fiber.StatusBadRequest
Status: fiber.StatusBadRequest,
}
// delete the task from the database // delete the task from the database
} else if err := tasks.Delete(taskID); err != nil { } else if err := tasks.Delete(taskID); err != nil {
logger.Error().Msgf("task-deletion failed: can't delete task from database: %v", err) logger.Error().Msgf("task-deletion failed: can't delete task from database: %v", err)
return responseMessage{ a.Status = fiber.StatusInternalServerError
Status: fiber.StatusInternalServerError,
}
} else {
return responseMessage{}
} }
} }

View File

@@ -5,76 +5,75 @@ import (
"github.com/johannesbuehl/golunteer/backend/pkg/db/users" "github.com/johannesbuehl/golunteer/backend/pkg/db/users"
) )
func getUsers(args HandlerArgs) responseMessage { func (a *Handler) getUsers() {
response := responseMessage{}
// check admin // check admin
if !args.User.Admin { if !a.Admin {
response.Status = fiber.StatusForbidden a.Status = fiber.StatusForbidden
logger.Log().Msgf("user is no admin") logger.Log().Msgf("user is no admin")
} else if users, err := users.Get(); err != nil { } else if users, err := users.Get(); err != nil {
response.Status = fiber.StatusInternalServerError a.Status = fiber.StatusInternalServerError
logger.Error().Msgf("can't get users: %v", err) logger.Error().Msgf("can't get users: %v", err)
} else { } else {
response.Data = users a.Data = users
}
} }
return response func (a *Handler) postUser() {
}
func postUser(args HandlerArgs) responseMessage {
response := responseMessage{}
// check admin // check admin
if !args.User.Admin { if !a.Admin {
response.Status = fiber.StatusForbidden a.Status = fiber.StatusForbidden
logger.Log().Msgf("user is no admin")
} else { } else {
// parse the body // parse the body
var body users.UserAdd var body users.UserAdd
if err := args.C.BodyParser(&body); err != nil { if err := a.C.BodyParser(&body); err != nil {
response.Status = fiber.StatusBadRequest a.Status = fiber.StatusBadRequest
logger.Warn().Msgf("can't parse body: %v", err) logger.Warn().Msgf("can't parse body: %v", err)
// validate the body // validate the body
} else if err := validate.Struct(body); err != nil { } else if err := validate.Struct(body); err != nil {
response.Status = fiber.StatusBadRequest a.Status = fiber.StatusBadRequest
logger.Warn().Msgf("invalid body: %v", err) logger.Warn().Msgf("invalid body: %v", err)
} else if err := users.Add(body); err != nil { } else if err := users.Add(body); err != nil {
response.Status = fiber.StatusInternalServerError a.Status = fiber.StatusInternalServerError
logger.Warn().Msgf("can't add user: %v", err) logger.Warn().Msgf("can't add user: %v", err)
} }
} }
return response
} }
func putPassword(args HandlerArgs) responseMessage { func (a *Handler) putPassword() {
response := responseMessage{}
// parse the body // parse the body
var body users.UserChangePassword var body users.UserChangePassword
if err := args.C.BodyParser(&body); err != nil { if err := a.C.BodyParser(&body); err != nil {
response.Status = fiber.StatusBadRequest
logger.Log().Msgf("can't parse body: %v", err) logger.Log().Msgf("can't parse body: %v", err)
a.Status = fiber.StatusBadRequest
// body has been parsed successfully
} else { } else {
body.UserName = args.User.UserName body.UserName = a.UserName
// validate the body
if err := validate.Struct(body); err != nil { if err := validate.Struct(body); err != nil {
response.Status = fiber.StatusBadRequest
logger.Info().Msgf("invalid body: %v", err) logger.Info().Msgf("invalid body: %v", err)
} else if tokenID, err := users.ChangePassword(body); err != nil {
response.Status = fiber.StatusInternalServerError
a.Status = fiber.StatusBadRequest
// send the password change to the database and get the new tokenID back
} else if tokenID, err := users.ChangePassword(body); err != nil {
logger.Error().Msgf("can't update password: %v", err) logger.Error().Msgf("can't update password: %v", err)
a.Status = fiber.StatusInternalServerError
// sign a new JWT with the new tokenID // sign a new JWT with the new tokenID
} else if jwt, err := config.SignJWT(JWTPayload{ } else if jwt, err := config.SignJWT(JWTPayload{
UserName: body.UserName, UserName: body.UserName,
@@ -82,23 +81,22 @@ func putPassword(args HandlerArgs) responseMessage {
// if something failed, remove the current session-cookie // if something failed, remove the current session-cookie
}); err != nil { }); err != nil {
args.removeSessionCookie() a.removeSessionCookie()
a.Status = fiber.StatusPartialContent
// set the new session-cookie // set the new session-cookie
} else { } else {
// update the token in the session-cookie // update the token in the session-cookie
args.setSessionCookie(&jwt) a.setSessionCookie(&jwt)
}
} }
} }
return response func (a *Handler) patchUser() {
}
func patchUser(args HandlerArgs) responseMessage {
response := responseMessage{}
// check admin // check admin
if !args.User.Admin { if !a.Admin {
response.Status = fiber.StatusForbidden a.Status = fiber.StatusForbidden
logger.Log().Msgf("user is no admin") logger.Log().Msgf("user is no admin")
} else { } else {
@@ -108,20 +106,20 @@ func patchUser(args HandlerArgs) responseMessage {
NewName string `json:"newName"` NewName string `json:"newName"`
} }
if err := args.C.BodyParser(&body); err != nil { if err := a.C.BodyParser(&body); err != nil {
response.Status = fiber.StatusBadRequest a.Status = fiber.StatusBadRequest
logger.Log().Msgf("can't parse body: %v", err) logger.Log().Msgf("can't parse body: %v", err)
// prevent to demoting self from admin // prevent to demoting self from admin
} else if !body.Admin && body.UserName == args.User.UserName { } else if !body.Admin && body.UserName == a.UserName {
response.Status = fiber.StatusBadRequest a.Status = fiber.StatusBadRequest
logger.Warn().Msgf("can't demote self from admin") logger.Warn().Msgf("can't demote self from admin")
} else { } else {
// check for an empty user-name // check for an empty user-name
if len(body.UserName) == 0 { if len(body.UserName) == 0 {
response.Status = fiber.StatusBadRequest a.Status = fiber.StatusBadRequest
logger.Warn().Msgf("username is empty") logger.Warn().Msgf("username is empty")
@@ -135,36 +133,36 @@ func patchUser(args HandlerArgs) responseMessage {
} }
if _, err = users.ChangePassword(usePasswordChange); err != nil { if _, err = users.ChangePassword(usePasswordChange); err != nil {
response.Status = fiber.StatusInternalServerError a.Status = fiber.StatusInternalServerError
logger.Error().Msgf("can't change password: %v", err) logger.Error().Msgf("can't change password: %v", err)
return response return
} }
} }
// only change the name, if it differs // only change the name, if it differs
if body.NewName != body.UserName { if body.NewName != body.UserName {
if err := users.ChangeName(body.UserName, body.NewName); err != nil { if err := users.ChangeName(body.UserName, body.NewName); err != nil {
response.Status = fiber.StatusInternalServerError a.Status = fiber.StatusInternalServerError
logger.Error().Msgf("can't change user-name: %v", err) logger.Error().Msgf("can't change user-name: %v", err)
return response return
} }
} }
// set the admin-status // set the admin-status
if err := users.SetAdmin(body.NewName, body.Admin); err != nil { if err := users.SetAdmin(body.NewName, body.Admin); err != nil {
response.Status = fiber.StatusInternalServerError a.Status = fiber.StatusInternalServerError
logger.Error().Msgf("updating admin-status failed: %v", err) logger.Error().Msgf("updating admin-status failed: %v", err)
} else { } else {
// if we modified ourself, update the session-cookie // if we modified ourself, update the session-cookie
if body.UserName == args.User.UserName { if body.UserName == a.UserName {
// get the tokenID // get the tokenID
if tokenID, err := users.TokenID(body.NewName); err != nil { if tokenID, err := users.TokenID(body.NewName); err != nil {
response.Status = fiber.StatusInternalServerError a.Status = fiber.StatusInternalServerError
logger.Error().Msgf("can't get tokenID: %v", err) logger.Error().Msgf("can't get tokenID: %v", err)
@@ -172,14 +170,15 @@ func patchUser(args HandlerArgs) responseMessage {
UserName: body.NewName, UserName: body.NewName,
TokenID: tokenID, TokenID: tokenID,
}); err != nil { }); err != nil {
response.Status = fiber.StatusInternalServerError a.Status = fiber.StatusInternalServerError
logger.Error().Msgf("JWT-signing failed: %v", err) logger.Error().Msgf("JWT-signing failed: %v", err)
// remove the session-cookie // remove the session-cookie
args.removeSessionCookie() a.removeSessionCookie()
} else { } else {
args.setSessionCookie(&jwt) a.setSessionCookie(&jwt)
}
} }
} }
} }
@@ -187,50 +186,35 @@ func patchUser(args HandlerArgs) responseMessage {
} }
} }
return response func (a *Handler) deleteUser() {
}
func deleteUser(args HandlerArgs) responseMessage {
// check admin // check admin
if !args.User.Admin { if !a.Admin {
logger.Warn().Msg("user-deletion failed: user is no admin") logger.Warn().Msg("user-deletion failed: user is no admin")
return responseMessage{ a.Status = fiber.StatusUnauthorized
Status: fiber.StatusUnauthorized,
}
// get the username from the query // get the username from the query
} else if userName := args.C.Query("userName"); userName == "" { } else if userName := a.C.Query("userName"); userName == "" {
logger.Log().Msg("user-deletion failed: query is missing \"userName\"") logger.Log().Msg("user-deletion failed: query is missing \"userName\"")
return responseMessage{ a.Status = fiber.StatusBadRequest
Status: fiber.StatusBadRequest,
}
// check wether the user tries to delete himself // check wether the user tries to delete himself
} else if userName == args.User.UserName { } else if userName == a.UserName {
logger.Log().Msg("user-deletion failed: self-deletion is illegal") logger.Log().Msg("user-deletion failed: self-deletion is illegal")
return responseMessage{ a.Status = fiber.StatusBadRequest
Status: fiber.StatusBadRequest,
}
// check wether the user tries to delete the admin // check wether the user tries to delete the admin
} else if userName == "admin" { } else if userName == "admin" {
logger.Log().Msg("user-deletion failed: admin-deletion is illegal") logger.Log().Msg("user-deletion failed: admin-deletion is illegal")
return responseMessage{ a.Status = fiber.StatusBadRequest
Status: fiber.StatusBadRequest,
}
// delete the user // delete the user
} else if err := users.Delete(userName); err != nil { } else if err := users.Delete(userName); err != nil {
logger.Error().Msgf("user-deletion failed: user doesn't exist") logger.Error().Msgf("user-deletion failed: user doesn't exist")
return responseMessage{ a.Status = fiber.StatusNotFound
Status: fiber.StatusNotFound,
}
} else {
return responseMessage{}
} }
} }

View File

@@ -1,18 +1,18 @@
CREATE TABLE IF NOT EXISTS TASKS ( CREATE TABLE IF NOT EXISTS TASKS (
id INTEGER PRIMARY KEY, taskID INTEGER PRIMARY KEY,
name varchar(64) NOT NULL, taskName varchar(64) NOT NULL,
enabled BOOL DEFAULT 1 enabled BOOL DEFAULT 1
); );
CREATE TABLE IF NOT EXISTS AVAILABILITIES ( CREATE TABLE IF NOT EXISTS AVAILABILITIES (
id INTEGER PRIMARY KEY, availabilityID INTEGER PRIMARY KEY,
name varchar(32) NOT NULL, availabilityName varchar(32) NOT NULL,
color varchar(7) NOT NULL, color varchar(7) NOT NULL,
enabled BOOL DEFAULT 1 enabled BOOL DEFAULT 1
); );
CREATE TABLE IF NOT EXISTS USERS ( CREATE TABLE IF NOT EXISTS USERS (
name varchar(64) PRIMARY KEY, userName varchar(64) PRIMARY KEY,
password BLOB NOT NULL, password BLOB NOT NULL,
admin BOOL NOT NULL DEFAULT(false), admin BOOL NOT NULL DEFAULT(false),
tokenID varchar(64) NOT NULL, tokenID varchar(64) NOT NULL,
@@ -21,7 +21,7 @@ CREATE TABLE IF NOT EXISTS USERS (
); );
CREATE TABLE IF NOT EXISTS EVENTS ( CREATE TABLE IF NOT EXISTS EVENTS (
id INTEGER PRIMARY KEY, eventID INTEGER PRIMARY KEY,
date DATETIME NOT NULL, date DATETIME NOT NULL,
description TEXT DEFAULT "" description TEXT DEFAULT ""
); );
@@ -31,9 +31,9 @@ CREATE TABLE IF NOT EXISTS USER_AVAILABILITIES (
eventID INTEGER NOT NULL, eventID INTEGER NOT NULL,
availabilityID INTEGER NOT NULL, availabilityID INTEGER NOT NULL,
PRIMARY KEY (userName, eventID), PRIMARY KEY (userName, eventID),
FOREIGN KEY (userName) REFERENCES USERS(name) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (userName) REFERENCES USERS(userName) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (eventID) REFERENCES EVENTS(id) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (eventID) REFERENCES EVENTS(eventID) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (availabilityID) REFERENCES AVAILABILITIES(id) ON UPDATE CASCADE FOREIGN KEY (availabilityID) REFERENCES AVAILABILITIES(availabilityID) ON UPDATE CASCADE
); );
CREATE TABLE IF NOT EXISTS USER_ASSIGNMENTS ( CREATE TABLE IF NOT EXISTS USER_ASSIGNMENTS (
@@ -41,7 +41,7 @@ CREATE TABLE IF NOT EXISTS USER_ASSIGNMENTS (
taskID INTEGER NOT NULL, taskID INTEGER NOT NULL,
userName varchar(64), userName varchar(64),
PRIMARY KEY (eventID, taskID), PRIMARY KEY (eventID, taskID),
FOREIGN KEY (eventID) REFERENCES EVENTS(id) ON DELETE CASCADE, FOREIGN KEY (eventID) REFERENCES EVENTS(eventID) ON DELETE CASCADE,
FOREIGN KEY (userName) REFERENCES USERS(name) ON DELETE CASCADE ON UPDATE CASCADE, FOREIGN KEY (userName) REFERENCES USERS(userName) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (taskID) REFERENCES TASKS(id) ON UPDATE CASCADE FOREIGN KEY (taskID) REFERENCES TASKS(taskID) ON UPDATE CASCADE
); );

View File

@@ -1,36 +0,0 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

View File

@@ -1,17 +1,19 @@
"use client"; "use client";
import { create } from "zustand"; import { create } from "zustand";
import { persist } from "zustand/middleware";
import { Task } from "./lib"; import { Task } from "./lib";
import { Availability } from "./app/admin/(availabilities)/AvailabilityEditor"; import { Availability } from "./app/admin/(availabilities)/AvailabilityEditor";
export interface EventData { export interface BaseEvent {
id: number; eventID: number;
date: string; date: string;
tasks: TaskAssignment[];
description: string; description: string;
} }
export type EventData = BaseEvent & {
tasks: TaskAssignment[];
};
interface TaskAssignment { interface TaskAssignment {
taskID: number; taskID: number;
taskName: string; taskName: string;
@@ -40,11 +42,10 @@ const initialState = {
}; };
const zustand = create<Zustand>()( const zustand = create<Zustand>()(
persist( // persist(
(set, get) => ({ (set, get) => ({
...initialState, ...initialState,
reset: (newZustand) => { reset: (newZustand) => {
console.debug("reset");
set({ set({
...initialState, ...initialState,
...newZustand, ...newZustand,
@@ -52,16 +53,16 @@ const zustand = create<Zustand>()(
}, },
patch: (patch) => set({ ...get(), ...patch }), patch: (patch) => set({ ...get(), ...patch }),
}), }),
{ // {
name: "golunteer-storage", // name: "golunteer-storage",
partialize: (state) => // partialize: (state) =>
Object.fromEntries( // Object.fromEntries(
Object.entries(state).filter(([key]) => // Object.entries(state).filter(([key]) =>
["user", "tasksList", "tasksMap"].includes(key), // ["user", "tasksList", "tasksMap"].includes(key),
), // ),
), // ),
}, // },
), // ),
); );
export default zustand; export default zustand;

View File

@@ -30,10 +30,7 @@ export default function Header({ sites }: { sites: SiteLink[] }) {
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const result = await apiCall<{ pendingEvents: number }>( const result = await apiCall<number>("GET", "events/user/pending/count");
"GET",
"events/user/pending",
);
if (result.ok) { if (result.ok) {
setPendingEvents(await result.json()); setPendingEvents(await result.json());

View File

@@ -0,0 +1,29 @@
"use client";
import { apiCall } from "@/lib";
import { EventData } from "@/Zustand";
import { useAsyncList } from "@react-stately/data";
export default function MyEvents() {
const events = useAsyncList({
async load() {
const result = await apiCall<EventData[]>("GET", "events/user/assigned");
if (result.ok) {
return {
items: await result.json(),
};
} else {
return {
items: [],
};
}
},
});
return (
<div>
<h2>{events.items.map((e) => e.date)}</h2>
</div>
);
}

View File

@@ -1,33 +1,17 @@
"use client"; import MyEvents from "./MyEvents";
import PengingEvents from "./PendingEvents";
import { Add } from "@carbon/icons-react";
import { useState } from "react";
import AddEvent from "../components/Event/AddEvent";
import { Button } from "@heroui/react";
export default function EventVolunteer() {
const [showAddItemDialogue, setShowAddItemDialogue] = useState(false);
export default function Overview() {
return ( return (
<div className="relative flex-1"> <div className="relative flex-1">
<h2 className="mb-4 text-center text-4xl">Overview</h2>
<div className="flex flex-wrap justify-center gap-4"></div> <div className="flex flex-wrap justify-center gap-4"></div>
<Button <h1 className="mb-4 text-center text-4xl">My Events</h1>
color="primary" <MyEvents />
isIconOnly <h1 className="mb-4 text-center text-4xl">
radius="full" events that I don't have entered an availability yet
className="absolute bottom-0 right-0" </h1>
onPress={() => setShowAddItemDialogue(true)} <PengingEvents />
>
<Add size={32} />
</Button>
<AddEvent
className="border-2 border-accent-3"
isOpen={showAddItemDialogue}
onOpenChange={setShowAddItemDialogue}
/>
</div> </div>
); );
} }

View File

@@ -0,0 +1,77 @@
"use client";
import AvailabilityChip from "@/components/AvailabilityChip";
import Event from "@/components/Event/Event";
import { apiCall, getAvailabilities } from "@/lib";
import { BaseEvent } from "@/Zustand";
import { Select, SelectItem } from "@heroui/react";
import { useAsyncList } from "@react-stately/data";
type EventAvailability = BaseEvent & {
availability: number;
};
export default function PengingEvents() {
// get the events the user hasn't yet inserted his availability for
const events = useAsyncList({
async load() {
const result = await apiCall<EventAvailability[]>(
"GET",
"events/user/pending",
);
if (result.ok) {
return {
items: await result.json(),
};
} else {
return {
items: [],
};
}
},
});
// the individual, selectable availabilities
const availabilities = useAsyncList({
async load() {
return {
items: (await getAvailabilities()).filter((a) => a.enabled),
};
},
});
return (
<div className="flex justify-center gap-4">
{events.items.map((e) => (
<Event key={e.eventID} event={e}>
<Select
items={availabilities.items}
label="Availability"
variant="bordered"
className="mt-auto"
isMultiline
renderValue={(availability) => (
<div>
{availability.map((a) =>
!!a.data ? (
<AvailabilityChip key={a.key} availability={a.data} />
) : null,
)}
</div>
)}
>
{(availability) => (
<SelectItem
key={availability.availabilityID}
textValue={availability.availabilityName}
>
<AvailabilityChip availability={availability} />
</SelectItem>
)}
</Select>
</Event>
))}
</div>
);
}

View File

@@ -40,7 +40,7 @@ export default function Availabilities() {
switch (sortDescriptor.column) { switch (sortDescriptor.column) {
case "text": case "text":
cmp = a.name.localeCompare(b.name); cmp = a.availabilityName.localeCompare(b.availabilityName);
break; break;
case "enabled": case "enabled":
if (a.enabled && !b.enabled) { if (a.enabled && !b.enabled) {
@@ -78,9 +78,11 @@ export default function Availabilities() {
availabilities.reload(); availabilities.reload();
} }
async function sendDeleteAvailability(id: number | undefined) { async function sendDeleteAvailability(availabilityID: number | undefined) {
if (id !== undefined) { if (availabilityID !== undefined) {
const result = await apiCall("DELETE", "availabilities", { id }); const result = await apiCall("DELETE", "availabilities", {
availabilityID,
});
if (result.ok) { if (result.ok) {
reload(); reload();
@@ -133,7 +135,7 @@ export default function Availabilities() {
</TableHeader> </TableHeader>
<TableBody items={availabilities.items}> <TableBody items={availabilities.items}>
{(availability) => ( {(availability) => (
<TableRow key={availability.name}> <TableRow key={availability.availabilityName}>
<TableCell> <TableCell>
<AvailabilityChip availability={availability} /> <AvailabilityChip availability={availability} />
</TableCell> </TableCell>
@@ -190,7 +192,9 @@ export default function Availabilities() {
!isOpen ? setDeleteAvailability(undefined) : null !isOpen ? setDeleteAvailability(undefined) : null
} }
itemName="Availability" itemName="Availability"
onDelete={() => sendDeleteAvailability(deleteAvailability?.id)} onDelete={() =>
sendDeleteAvailability(deleteAvailability?.availabilityID)
}
> >
{!!deleteAvailability ? ( {!!deleteAvailability ? (
<> <>

View File

@@ -1,4 +1,5 @@
import ColorSelector from "@/components/Colorselector"; import ColorSelector from "@/components/Colorselector";
import { AllString } from "@/lib";
import { import {
Checkbox, Checkbox,
Form, Form,
@@ -12,9 +13,9 @@ import {
import React, { FormEvent, useEffect, useState } from "react"; import React, { FormEvent, useEffect, useState } from "react";
export interface Availability { export interface Availability {
name: string; availabilityName: string;
color: string; color: string;
id: number | undefined; availabilityID: number | undefined;
enabled: boolean; enabled: boolean;
} }
@@ -26,7 +27,7 @@ export default function AvailabilityEditor(props: {
onOpenChange?: (isOpen: boolean) => void; onOpenChange?: (isOpen: boolean) => void;
onSubmit?: (e: Availability) => void; onSubmit?: (e: Availability) => void;
}) { }) {
const [name, setName] = useState(props.value?.name ?? ""); const [name, setName] = useState(props.value?.availabilityName ?? "");
const [color, setColor] = useState(props.value?.color ?? "Red"); const [color, setColor] = useState(props.value?.color ?? "Red");
const [enabled, setEnabled] = useState(props.value?.enabled ?? true); const [enabled, setEnabled] = useState(props.value?.enabled ?? true);
@@ -40,15 +41,13 @@ export default function AvailabilityEditor(props: {
}, [props.isOpen]); }, [props.isOpen]);
function submit(e: FormEvent<HTMLFormElement>) { function submit(e: FormEvent<HTMLFormElement>) {
const formData = Object.fromEntries(new FormData(e.currentTarget)) as { const formData = Object.fromEntries(
name: string; new FormData(e.currentTarget),
color: string; ) as AllString<Exclude<Availability, "availabilityID">>;
enabled: string;
};
props.onSubmit?.({ props.onSubmit?.({
...formData, ...formData,
id: props.value?.id, availabilityID: props.value?.availabilityID,
enabled: formData.enabled == "true", enabled: formData.enabled == "true",
}); });
} }
@@ -77,7 +76,7 @@ export default function AvailabilityEditor(props: {
<Input <Input
value={name} value={name}
onValueChange={setName} onValueChange={setName}
name="name" name="availabilityName"
label="Name" label="Name"
isRequired isRequired
variant="bordered" variant="bordered"

View File

@@ -21,7 +21,7 @@ export default function EditAvailability(props: {
return ( return (
<AvailabilityEditor <AvailabilityEditor
key={props.value?.id} key={props.value?.availabilityID}
header={ header={
<> <>
Edit Availability{" "} Edit Availability{" "}

View File

@@ -20,12 +20,12 @@ export default function EditTask(props: {
return ( return (
<TaskEditor <TaskEditor
key={props.value?.name} key={props.value?.taskName}
header={ header={
<> <>
Edit Task{" "} Edit Task{" "}
<span className="font-numbers font-normal italic"> <span className="font-numbers font-normal italic">
&quot;{props.value?.name}&quot; &quot;{props.value?.taskName}&quot;
</span> </span>
</> </>
} }

View File

@@ -1,4 +1,4 @@
import { Task } from "@/lib"; import { AllString, Task } from "@/lib";
import { import {
Checkbox, Checkbox,
Form, Form,
@@ -19,7 +19,7 @@ export default function TaskEditor(props: {
onOpenChange?: (isOpen: boolean) => void; onOpenChange?: (isOpen: boolean) => void;
onSubmit?: (e: Task) => void; onSubmit?: (e: Task) => void;
}) { }) {
const [name, setName] = useState(props.value?.name ?? ""); const [name, setName] = useState(props.value?.taskName ?? "");
const [enabled, setEnabled] = useState(props.value?.enabled ?? true); const [enabled, setEnabled] = useState(props.value?.enabled ?? true);
// clear the inputs on closing // clear the inputs on closing
@@ -31,14 +31,13 @@ export default function TaskEditor(props: {
}, [props.isOpen]); }, [props.isOpen]);
function submit(e: FormEvent<HTMLFormElement>) { function submit(e: FormEvent<HTMLFormElement>) {
const formData = Object.fromEntries(new FormData(e.currentTarget)) as { const formData = Object.fromEntries(
name: string; new FormData(e.currentTarget),
enabled: string; ) as AllString<Exclude<Task, "taskID">>;
};
props.onSubmit?.({ props.onSubmit?.({
...formData, ...formData,
id: props.value?.id, taskID: props.value?.taskID,
enabled: formData.enabled == "true", enabled: formData.enabled == "true",
}); });
} }
@@ -68,7 +67,7 @@ export default function TaskEditor(props: {
<Input <Input
value={name} value={name}
onValueChange={setName} onValueChange={setName}
name="name" name="taskName"
label="Name" label="Name"
isRequired isRequired
variant="bordered" variant="bordered"

View File

@@ -47,7 +47,7 @@ export default function Tasks() {
switch (sortDescriptor.column) { switch (sortDescriptor.column) {
case "text": case "text":
cmp = a.name.localeCompare(b.name); cmp = a.taskName.localeCompare(b.taskName);
break; break;
case "enabled": case "enabled":
if (a.enabled && !b.enabled) { if (a.enabled && !b.enabled) {
@@ -76,9 +76,9 @@ export default function Tasks() {
tasks.reload(); tasks.reload();
} }
async function sendDeleteTask(id: number | undefined) { async function sendDeleteTask(taskID: number | undefined) {
if (id !== undefined) { if (taskID !== undefined) {
const result = await apiCall("DELETE", "tasks", { id }); const result = await apiCall("DELETE", "tasks", { taskID });
if (result.ok) { if (result.ok) {
tasks.reload(); tasks.reload();
@@ -130,8 +130,8 @@ export default function Tasks() {
</TableHeader> </TableHeader>
<TableBody items={tasks.items}> <TableBody items={tasks.items}>
{(task) => ( {(task) => (
<TableRow key={task.id}> <TableRow key={task.taskID}>
<TableCell>{task.name}</TableCell> <TableCell>{task.taskName}</TableCell>
<TableCell> <TableCell>
<Checkbox isSelected={task.enabled} /> <Checkbox isSelected={task.enabled} />
</TableCell> </TableCell>
@@ -181,13 +181,13 @@ export default function Tasks() {
isOpen={!!deleteTask} isOpen={!!deleteTask}
onOpenChange={(isOpen) => (!isOpen ? setDeleteTask(undefined) : null)} onOpenChange={(isOpen) => (!isOpen ? setDeleteTask(undefined) : null)}
itemName="Task" itemName="Task"
onDelete={() => sendDeleteTask(deleteTask?.id)} onDelete={() => sendDeleteTask(deleteTask?.taskID)}
> >
{!!deleteTask ? ( {!!deleteTask ? (
<> <>
The task{" "} The task{" "}
<span className="font-numbers text-accent-1"> <span className="font-numbers text-accent-1">
{deleteTask.name} {deleteTask.taskName}
</span>{" "} </span>{" "}
will be deleted. will be deleted.
</> </>

View File

@@ -1,4 +1,8 @@
import { classNames, validatePassword as validatePassword } from "@/lib"; import {
AllString,
classNames,
validatePassword as validatePassword,
} from "@/lib";
import zustand, { User, UserAddModify } from "@/Zustand"; import zustand, { User, UserAddModify } from "@/Zustand";
import { import {
Checkbox, Checkbox,
@@ -29,11 +33,9 @@ export default function UserEditor(props: {
// update the user in the backend // update the user in the backend
async function submit(e: FormEvent<HTMLFormElement>) { async function submit(e: FormEvent<HTMLFormElement>) {
const formData = Object.fromEntries(new FormData(e.currentTarget)) as { const formData = Object.fromEntries(
userName: string; new FormData(e.currentTarget),
password: string; ) as AllString<UserAddModify>;
admin: string;
};
const data = { const data = {
...formData, ...formData,

View File

@@ -62,8 +62,8 @@ export default function AdminPanel() {
...tasks ...tasks
.filter((task) => task.enabled) .filter((task) => task.enabled)
.map((task) => ({ .map((task) => ({
label: task.name, label: task.taskName,
key: task.id ?? -1, key: task.taskID ?? -1,
align: "center", align: "center",
})), })),
{ key: "actions", label: "Action", align: "center" }, { key: "actions", label: "Action", align: "center" },
@@ -116,7 +116,9 @@ export default function AdminPanel() {
// send a delete request to the backend and close the popup on success // send a delete request to the backend and close the popup on success
async function sendDeleteEvent() { async function sendDeleteEvent() {
if (deleteEvent !== undefined) { if (deleteEvent !== undefined) {
const result = await apiCall("DELETE", "event", { id: deleteEvent.id }); const result = await apiCall("DELETE", "event", {
eventID: deleteEvent.eventID,
});
if (result.ok) { if (result.ok) {
// store the received events // store the received events
@@ -262,7 +264,7 @@ export default function AdminPanel() {
</TableHeader> </TableHeader>
<TableBody items={events.items} emptyContent={"No events scheduled"}> <TableBody items={events.items} emptyContent={"No events scheduled"}>
{(event) => ( {(event) => (
<TableRow key={event.id}> <TableRow key={event.eventID}>
{(columnKey) => ( {(columnKey) => (
<TableCell>{getKeyValue(event, columnKey)}</TableCell> <TableCell>{getKeyValue(event, columnKey)}</TableCell>
)} )}

View File

@@ -15,13 +15,11 @@ export default function Events() {
const events = useAsyncList<EventData>({ const events = useAsyncList<EventData>({
async load() { async load() {
const result = await apiCall("GET", "events/assignments"); const result = await apiCall<EventData[]>("GET", "events/assignments");
if (result.ok) { if (result.ok) {
const data = await result.json(); const data = await result.json();
console.debug(data);
return { return {
items: data, items: data,
}; };
@@ -62,6 +60,7 @@ export default function Events() {
className="border-2 border-accent-3" className="border-2 border-accent-3"
isOpen={showAddItemDialogue} isOpen={showAddItemDialogue}
onOpenChange={setShowAddItemDialogue} onOpenChange={setShowAddItemDialogue}
onSuccess={events.reload}
/> />
</> </>
) : null} ) : null}

View File

@@ -1,5 +1,5 @@
import EventVolunteer from "./Overview"; import Overview from "./Overview";
export default function Home() { export default function Home() {
return <EventVolunteer />; return <Overview />;
} }

View File

@@ -17,7 +17,7 @@ export default function AvailabilityChip({
}} }}
className={className} className={className}
> >
{availability.name} {availability.availabilityName}
</Chip> </Chip>
); );
} }

View File

@@ -24,7 +24,7 @@ export default function EditEvent(props: {
return ( return (
<EventEditor <EventEditor
value={props.value} value={props.value}
key={props.value?.id} key={props.value?.eventID}
header="Edit Event" header="Edit Event"
isOpen={props.isOpen} isOpen={props.isOpen}
onOpenChange={props.onOpenChange} onOpenChange={props.onOpenChange}

View File

@@ -1,7 +1,7 @@
"use client"; "use client";
import LocalDate from "../LocalDate"; import LocalDate from "../LocalDate";
import { EventData } from "@/Zustand"; import { BaseEvent } from "@/Zustand";
import { Card, CardBody, CardHeader, Divider } from "@heroui/react"; import { Card, CardBody, CardHeader, Divider } from "@heroui/react";
import React from "react"; import React from "react";
@@ -9,7 +9,7 @@ export default function Event({
event, event,
children, children,
}: { }: {
event: EventData; event: BaseEvent;
children?: React.ReactNode; children?: React.ReactNode;
}) { }) {
return ( return (

View File

@@ -18,10 +18,12 @@ import {
Spinner, Spinner,
Textarea, Textarea,
} from "@heroui/react"; } from "@heroui/react";
import zustand, { EventData } from "@/Zustand"; import { EventData } from "@/Zustand";
import { useAsyncList } from "@react-stately/data";
import { getTasks } from "@/lib";
export interface EventSubmitData { export interface EventSubmitData {
id: number; eventID: number;
date: string; date: string;
description: string; description: string;
tasks: number[]; tasks: number[];
@@ -47,12 +49,19 @@ export default function EventEditor(props: {
const [eventTasks, setEventTasks] = useState<string[]>( const [eventTasks, setEventTasks] = useState<string[]>(
props.value?.tasks.map((k) => k.taskID.toString()) ?? [], props.value?.tasks.map((k) => k.taskID.toString()) ?? [],
); );
const tasks = zustand((state) => state.tasks);
const tasks = useAsyncList({
async load() {
return {
items: await getTasks(),
};
},
});
function onSubmit() { function onSubmit() {
if (!!props.onSubmit && !!date) { if (!!props.onSubmit && !!date) {
props.onSubmit({ props.onSubmit({
id: props.value?.id ?? -1, eventID: props.value?.eventID ?? -1,
date: date.toAbsoluteString(), date: date.toAbsoluteString(),
description, description,
tasks: eventTasks.map((t) => parseInt(t)), tasks: eventTasks.map((t) => parseInt(t)),
@@ -109,12 +118,12 @@ export default function EventEditor(props: {
} }
> >
{!!tasks ? ( {!!tasks ? (
tasks tasks.items
?.filter((task) => task.enabled) ?.filter((task) => task.enabled)
.map((task) => ( .map((task) => (
<div key={task.id}> <div key={task.taskID}>
<Checkbox value={task.id?.toString()}> <Checkbox value={task.taskID?.toString()}>
{task.name} {task.taskName}
</Checkbox> </Checkbox>
</div> </div>
)) ))

View File

@@ -2,30 +2,32 @@ import { DateFormatter as IntlDateFormatter } from "@internationalized/date";
import zustand from "./Zustand"; import zustand from "./Zustand";
import { Availability } from "./app/admin/(availabilities)/AvailabilityEditor"; import { Availability } from "./app/admin/(availabilities)/AvailabilityEditor";
export type AllString<T> = { [K in keyof T]: string };
type QueryParams = Record<string, string | { toString(): string }>; type QueryParams = Record<string, string | { toString(): string }>;
export type APICallResult<T extends object> = Response & { export type APICallResult<T> = Omit<Response, "json"> & {
json: () => Promise<T>; json: () => Promise<T>;
}; };
export async function apiCall<K extends object>( export async function apiCall<K>(
method: "GET", method: "GET",
api: string, api: string,
query?: QueryParams, query?: QueryParams,
): Promise<APICallResult<K>>; ): Promise<APICallResult<K>>;
export async function apiCall<K extends object>( export async function apiCall<K>(
method: "POST" | "PATCH" | "PUT", method: "POST" | "PATCH" | "PUT",
api: string, api: string,
query?: QueryParams, query?: QueryParams,
body?: object, body?: object,
): Promise<APICallResult<K>>; ): Promise<APICallResult<K>>;
export async function apiCall<K extends object>( export async function apiCall<K>(
method: "DELETE", method: "DELETE",
api: string, api: string,
query?: QueryParams, query?: QueryParams,
body?: object, body?: object,
): Promise<APICallResult<K>>; ): Promise<APICallResult<K>>;
export async function apiCall<K extends object>( export async function apiCall<K>(
method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE", method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE",
api: string, api: string,
query?: QueryParams, query?: QueryParams,
@@ -96,8 +98,8 @@ export function validatePassword(password: string): string[] {
} }
export interface Task { export interface Task {
id: number | undefined; taskID: number | undefined;
name: string; taskName: string;
enabled: boolean; enabled: boolean;
} }
@@ -105,7 +107,7 @@ export async function getTask(name: string): Promise<Task | undefined> {
// get the tasks // get the tasks
const tasks = await getTasks(); const tasks = await getTasks();
return tasks.find((t) => t.name === name); return tasks.find((t) => t.taskName === name);
} }
export async function getTasks(): Promise<Task[]> { export async function getTasks(): Promise<Task[]> {
@@ -136,7 +138,7 @@ export async function getAvailabilities(): Promise<Availability[]> {
if (!!state.availabilities) { if (!!state.availabilities) {
return state.availabilities; return state.availabilities;
} else { } else {
const result = await apiCall<Task[]>("GET", "availabilities"); const result = await apiCall<Availability[]>("GET", "availabilities");
if (result.ok) { if (result.ok) {
const tasks = await result.json(); const tasks = await result.json();