Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

yet more implementation #90

Merged
merged 4 commits into from
Nov 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions api/openapi-spec/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -561,7 +561,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
description: You do not have permission to do this
description: You do not have permission to do this. This includes cases where the attendee does not have attending status.
content:
application/json:
schema:
Expand All @@ -573,7 +573,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'409':
description: Duplicate assignment, this attendee is already in another group.
description: Duplicate assignment, or this attendee is already in another group.
content:
application/json:
schema:
Expand Down Expand Up @@ -1125,7 +1125,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'409':
description: Duplicate assignment, this attendee is already in another room, or the room is full.
description: Duplicate assignment to same room, or this attendee is already in another room, or not in attending status, or the room is full.
content:
application/json:
schema:
Expand Down Expand Up @@ -1490,7 +1490,7 @@ components:
- room.occupant.notfound (attendee is not in any/this room)
- room.not.empty (cannot delete a room that isn't empty)
- room.read.error (database error)
- room.size.full (not enough space in room to add another member)
- room.size.full (not enough space in room to add another attendee)
- room.size.too.small (cannot reduce room size, new size not big enough for current number of occupants)
- room.write.error (database error)
example: group.read.error
Expand Down
14 changes: 7 additions & 7 deletions internal/application/common/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,31 +69,31 @@ const (

// construct specific API errors

func NewBadRequest(ctx context.Context, message ErrorMessageCode, details url.Values, internalCauses ...error) APIError {
func NewBadRequest(ctx context.Context, message ErrorMessageCode, details url.Values, internalCauses ...error) error {
return NewAPIError(ctx, http.StatusBadRequest, message, details, internalCauses...)
}

func NewUnauthorized(ctx context.Context, message ErrorMessageCode, details url.Values, internalCauses ...error) APIError {
func NewUnauthorized(ctx context.Context, message ErrorMessageCode, details url.Values, internalCauses ...error) error {
return NewAPIError(ctx, http.StatusUnauthorized, message, details, internalCauses...)
}

func NewForbidden(ctx context.Context, message ErrorMessageCode, details url.Values, internalCauses ...error) APIError {
func NewForbidden(ctx context.Context, message ErrorMessageCode, details url.Values, internalCauses ...error) error {
return NewAPIError(ctx, http.StatusForbidden, message, details, internalCauses...)
}

func NewNotFound(ctx context.Context, message ErrorMessageCode, details url.Values, internalCauses ...error) APIError {
func NewNotFound(ctx context.Context, message ErrorMessageCode, details url.Values, internalCauses ...error) error {
return NewAPIError(ctx, http.StatusNotFound, message, details, internalCauses...)
}

func NewConflict(ctx context.Context, message ErrorMessageCode, details url.Values, internalCauses ...error) APIError {
func NewConflict(ctx context.Context, message ErrorMessageCode, details url.Values, internalCauses ...error) error {
return NewAPIError(ctx, http.StatusConflict, message, details, internalCauses...)
}

func NewInternalServerError(ctx context.Context, message ErrorMessageCode, details url.Values, internalCauses ...error) APIError {
func NewInternalServerError(ctx context.Context, message ErrorMessageCode, details url.Values, internalCauses ...error) error {
return NewAPIError(ctx, http.StatusInternalServerError, message, details, internalCauses...)
}

func NewBadGateway(ctx context.Context, message ErrorMessageCode, details url.Values, internalCauses ...error) APIError {
func NewBadGateway(ctx context.Context, message ErrorMessageCode, details url.Values, internalCauses ...error) error {
return NewAPIError(ctx, http.StatusBadGateway, message, details, internalCauses...)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/controller/v1/groupsctl/groups_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func (h *Controller) FindMyGroup(ctx context.Context, req *FindMyGroupRequest, w
}

func (h *Controller) FindMyGroupRequest(r *http.Request, w http.ResponseWriter) (*FindMyGroupRequest, error) {
// Endpoint only requires JWT token for now.
// Endpoint only requires logged-in user
return &FindMyGroupRequest{}, nil
}

Expand Down
90 changes: 75 additions & 15 deletions internal/controller/v1/roomsctl/rooms_get.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"github.com/eurofurence/reg-room-service/internal/application/common"
"github.com/eurofurence/reg-room-service/internal/application/web"
"github.com/eurofurence/reg-room-service/internal/controller/v1/util"
roomservice "github.com/eurofurence/reg-room-service/internal/service/rooms"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"net/http"
Expand All @@ -12,24 +14,76 @@ import (
modelsv1 "github.com/eurofurence/reg-room-service/internal/api/v1"
)

type ListRoomsRequest struct {
OccupantIDs []int64
MinSize uint
MaxSize uint
MinOccupants uint
MaxOccupants uint
}
func (h *Controller) ListRooms(ctx context.Context, req *roomservice.FindRoomParams, w http.ResponseWriter) (*modelsv1.RoomList, error) {
rooms, err := h.svc.FindRooms(ctx, req)
if err != nil {
return nil, err
}

func (h *Controller) ListRooms(ctx context.Context, req *ListRoomsRequest, w http.ResponseWriter) (*modelsv1.RoomList, error) {
return nil, nil
return &modelsv1.RoomList{
Rooms: rooms,
}, nil
}

func (h *Controller) ListRoomsRequest(r *http.Request, w http.ResponseWriter) (*ListRoomsRequest, error) {
return nil, nil
func (h *Controller) ListRoomsRequest(r *http.Request, w http.ResponseWriter) (*roomservice.FindRoomParams, error) {
var req roomservice.FindRoomParams

ctx := r.Context()
query := r.URL.Query()

queryIDs := query.Get("occupant_ids")
memberIDs, err := util.ParseMemberIDs(queryIDs)
if err != nil {
return nil, common.NewBadRequest(ctx, common.RequestParseFailed, common.Details(err.Error()))
}
req.MemberIDs = memberIDs

if minSize := query.Get("min_size"); minSize != "" {
val, err := util.ParseUInt[uint](minSize)
if err != nil {
return nil, common.NewBadRequest(ctx, common.RequestParseFailed, common.Details(err.Error()))
}

req.MinSize = val
}

if maxSize := query.Get("max_size"); maxSize != "" {
val, err := util.ParseUInt[uint](maxSize)
if err != nil {
return nil, common.NewBadRequest(ctx, common.RequestParseFailed, common.Details(err.Error()))
}

req.MaxSize = val
}

if minOccupants := query.Get("min_occupants"); minOccupants != "" {
val, err := util.ParseUInt[uint](minOccupants)
if err != nil {
return nil, common.NewBadRequest(ctx, common.RequestParseFailed, common.Details(err.Error()))
}

req.MinOccupants = val
}

if maxOccupants := query.Get("max_occupants"); maxOccupants != "" {
val, err := util.ParseInt[int](maxOccupants)
if err != nil {
return nil, common.NewBadRequest(ctx, common.RequestParseFailed, common.Details(err.Error()))
}
if val < -1 {
return nil, common.NewBadRequest(ctx, common.RequestParseFailed, common.Details("max_occupants cannot be less than -1"))
}

req.MaxOccupants = val
} else {
req.MaxOccupants = -1
}

return &req, nil
}

func (h *Controller) ListRoomsResponse(ctx context.Context, res *modelsv1.RoomList, w http.ResponseWriter) error {
return nil
return web.EncodeWithStatus(http.StatusOK, res, w)
}

type FindMyRoomRequest struct{}
Expand All @@ -38,15 +92,21 @@ type FindMyRoomRequest struct{}
//
// See OpenAPI Spec for further details.
func (h *Controller) FindMyRoom(ctx context.Context, req *FindMyRoomRequest, w http.ResponseWriter) (*modelsv1.Room, error) {
return nil, nil
room, err := h.svc.FindMyRoom(ctx)
if err != nil {
return nil, err
}

return room, nil
}

func (h *Controller) FindMyRoomRequest(r *http.Request, w http.ResponseWriter) (*FindMyRoomRequest, error) {
return nil, nil
// Endpoint only requires logged-in user
return &FindMyRoomRequest{}, nil
}

func (h *Controller) FindMyRoomResponse(ctx context.Context, res *modelsv1.Room, w http.ResponseWriter) error {
return nil
return web.EncodeWithStatus(http.StatusOK, res, w)
}

type GetRoomByIDRequest struct {
Expand Down
4 changes: 2 additions & 2 deletions internal/repository/database/inmemorydb/implementation.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,8 +329,8 @@ func (r *InMemoryRepository) UpdateRoom(ctx context.Context, room *entity.Room)
func (r *InMemoryRepository) GetRoomByID(ctx context.Context, id string) (*entity.Room, error) {
// allow deleted so history and undelete work
if result, ok := r.rooms[id]; ok {
grpCopy := result.Room
return &grpCopy, nil
roomCopy := result.Room
return &roomCopy, nil
} else {
return &entity.Room{}, gorm.ErrRecordNotFound
}
Expand Down
3 changes: 3 additions & 0 deletions internal/repository/database/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ type Repository interface {
//
// A room matches the list of badge numbers in anyOfMemberID if at least one of those badge numbers
// is in the room. An empty list or nil means no condition.
//
// For minOccupancy, minSize, maxSize a value of 0 means no condition (because all rooms satisfy these),
// for maxOccupancy a value of -1 means no condition (maxOccupancy=0 searches for empty rooms).
FindRooms(ctx context.Context, name string, minOccupancy uint, maxOccupancy int, minSize uint, maxSize uint, anyOfMemberID []int64) ([]string, error)
// GetRooms returns all rooms.
GetRooms(ctx context.Context) ([]*entity.Room, error)
Expand Down
69 changes: 69 additions & 0 deletions internal/service/rooms/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package roomservice

import (
"context"
"errors"
aulogging "github.com/StephanHCB/go-autumn-logging"
"github.com/eurofurence/reg-room-service/internal/application/common"
"github.com/eurofurence/reg-room-service/internal/repository/downstreams"
"github.com/eurofurence/reg-room-service/internal/repository/downstreams/attendeeservice"
)

func (r *roomService) loggedInUserValidRegistrationBadgeNo(ctx context.Context) (attendeeservice.Attendee, error) {
myRegIDs, err := r.AttSrv.ListMyRegistrationIds(ctx)
if err != nil {
aulogging.WarnErrf(ctx, err, "failed to obtain registrations for currently logged in user: %s", err.Error())
return attendeeservice.Attendee{}, common.NewBadGateway(ctx, common.DownstreamAttSrv, common.Details("downstream error when contacting attendee service"))
}
if len(myRegIDs) == 0 {
aulogging.InfoErr(ctx, err, "currently logged in user has no registrations - cannot be in a group")
return attendeeservice.Attendee{}, common.NewForbidden(ctx, common.NoSuchAttendee, common.Details("you do not have a valid registration"))
}
myID := myRegIDs[0]

if err := r.checkAttending(ctx, myID, common.NewForbidden(ctx, common.NotAttending, common.Details("registration is not in attending status"))); err != nil {
return attendeeservice.Attendee{}, err
}

attendee, err := r.AttSrv.GetAttendee(ctx, myID)
if err != nil {
return attendeeservice.Attendee{}, err
}
// ensure ID set in Attendee
attendee.ID = myID

return attendee, nil
}

func (r *roomService) validateRequestedAttendee(ctx context.Context, badgeNo int64) (attendeeservice.Attendee, error) {
if badgeNo <= 0 {
return attendeeservice.Attendee{}, common.NewBadRequest(ctx, common.RoomDataInvalid, common.Details("attendee badge number must be positive integer"))
}

attendee, err := r.AttSrv.GetAttendee(ctx, badgeNo)
if err != nil {
if errors.Is(err, downstreams.ErrDownStreamNotFound) {
return attendeeservice.Attendee{}, common.NewNotFound(ctx, common.NoSuchAttendee, common.Details("no such attendee"))
} else {
aulogging.WarnErrf(ctx, err, "failed to query for attendee with badge number %d: %s", badgeNo, err.Error())
return attendeeservice.Attendee{}, common.NewBadGateway(ctx, common.DownstreamAttSrv, common.Details("failed to look up invited attendee - internal error, see logs for details"))
}
}

return attendee, nil
}

func (r *roomService) checkAttending(ctx context.Context, badgeNo int64, notAttendingErr error) error {
status, err := r.AttSrv.GetStatus(ctx, badgeNo)
if err != nil {
aulogging.WarnErrf(ctx, err, "failed to obtain status for badge number %d: %s", badgeNo, err.Error())
return common.NewBadGateway(ctx, common.DownstreamAttSrv, common.Details("downstream error when contacting attendee service"))
}

switch status {
case attendeeservice.StatusApproved, attendeeservice.StatusPartiallyPaid, attendeeservice.StatusPaid, attendeeservice.StatusCheckedIn:
return nil
default:
return notAttendingErr
}
}
17 changes: 12 additions & 5 deletions internal/service/rooms/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,24 @@ type Service interface {
RemoveOccupantFromRoom(ctx context.Context, roomID string, badgeNumber int64) error

FindRooms(ctx context.Context, params *FindRoomParams) ([]*modelsv1.Room, error)
// FindMyRoom looks up the room the currently logged-in user is in.
//
// This works for admins just like for normal users, returning their room,
// but will fail for requests using an API Token (no currently logged-in user available).
//
// Only finds rooms that have the "final" flag, and only works if the user's registration has
// attending status.
FindMyRoom(ctx context.Context) (*modelsv1.Room, error)
}

type FindRoomParams struct {
memberIDs []int64
MemberIDs []int64 // empty list or nil means no condition

minSize uint
maxSize uint
MinSize uint // 0 means no condition
MaxSize uint // 0 means no condition

minOccupants uint
maxOccupants uint
MinOccupants uint // 0 means no condition
MaxOccupants int // -1 means no condition, 0 means search for empty rooms only
}

func New(db database.Repository, attsrv attendeeservice.AttendeeService, mailsrv mailservice.MailService) Service {
Expand Down
Loading
Loading