Skip to content
Open
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
Binary file added .DS_Store
Binary file not shown.
243 changes: 243 additions & 0 deletions backend/docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,9 @@ definitions:
hotel_id:
example: org_521e8400-e458-41d4-a716-446655440000
type: string
location_display:
example: North wing, 12B
type: string
name:
example: room cleaning
type: string
Expand Down Expand Up @@ -471,6 +474,9 @@ definitions:
id:
example: 530e8400-e458-41d4-a716-446655440000
type: string
location_display:
example: North wing, 12B
type: string
name:
example: room cleaning
type: string
Expand Down Expand Up @@ -674,6 +680,25 @@ definitions:
name:
type: string
type: object
github_com_generate_selfserve_internal_models.CreateTaskBody:
properties:
assign_to_me:
type: boolean
department:
type: string
description:
type: string
name:
type: string
priority:
enum:
- low
- medium
- high
type: string
required:
- name
type: object
github_com_generate_selfserve_internal_models.FloorSortOrder:
enum:
- ascending
Expand All @@ -698,6 +723,18 @@ definitions:
x-enum-varnames:
- TypeTaskAssigned
- TypeHighPriorityTask
github_com_generate_selfserve_internal_models.PatchTaskBody:
properties:
status:
enum:
- pending
- assigned
- in progress
- completed
type: string
required:
- status
type: object
github_com_generate_selfserve_internal_models.RequestSortOrder:
enum:
- high_to_low
Expand All @@ -708,6 +745,27 @@ definitions:
- RequestSortHighToLow
- RequestSortLowToHigh
- RequestSortUrgent
github_com_generate_selfserve_internal_models.Task:
properties:
department:
type: string
description:
type: string
due_time:
type: string
id:
type: string
is_assigned:
type: boolean
location:
type: string
priority:
type: string
status:
type: string
title:
type: string
type: object
github_com_generate_selfserve_internal_models.UpdateDepartment:
properties:
name:
Expand All @@ -725,6 +783,18 @@ definitions:
description: nil when no more pages
type: string
type: object
github_com_generate_selfserve_internal_utils.CursorPage-github_com_generate_selfserve_internal_models_Task:
properties:
has_more:
type: boolean
items:
items:
$ref: '#/definitions/github_com_generate_selfserve_internal_models.Task'
type: array
next_cursor:
description: nil when no more pages
type: string
type: object
internal_handler.AddEmployeeDepartmentBody:
properties:
department_id:
Expand Down Expand Up @@ -1797,6 +1867,179 @@ paths:
summary: Get presigned URL for profile picture upload
tags:
- s3
/tasks:
get:
description: Cursor-paginated tasks for my work or the unassigned pool
parameters:
- description: my or unassigned
in: query
name: tab
required: true
type: string
- description: Page size (default 20)
in: query
name: limit
type: integer
- description: Opaque cursor
in: query
name: cursor
type: string
- description: Filter by status
in: query
name: status
type: string
- description: Filter by department (case-insensitive)
in: query
name: department
type: string
- description: Filter by priority
in: query
name: priority
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/github_com_generate_selfserve_internal_utils.CursorPage-github_com_generate_selfserve_internal_models_Task'
"400":
description: Bad Request
schema:
$ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError'
security:
- BearerAuth: []
summary: List staff tasks
tags:
- tasks
post:
consumes:
- application/json
description: Creates a lightweight adhoc request for the authenticated user's
hotel
parameters:
- description: Task
in: body
name: body
required: true
schema:
$ref: '#/definitions/github_com_generate_selfserve_internal_models.CreateTaskBody'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/Request'
"400":
description: Bad Request
schema:
$ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError'
security:
- BearerAuth: []
summary: Create adhoc staff task
tags:
- tasks
/tasks/{id}:
patch:
consumes:
- application/json
parameters:
- description: Request id
in: path
name: id
required: true
type: string
- description: Patch
in: body
name: body
required: true
schema:
$ref: '#/definitions/github_com_generate_selfserve_internal_models.PatchTaskBody'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/Request'
"400":
description: Bad Request
schema:
$ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError'
"404":
description: Not Found
schema:
$ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError'
"409":
description: Conflict
schema:
$ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError'
security:
- BearerAuth: []
summary: Update task status
tags:
- tasks
/tasks/{id}/claim:
post:
parameters:
- description: Request id
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/Request'
"404":
description: Not Found
schema:
$ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError'
"409":
description: Conflict
schema:
$ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError'
security:
- BearerAuth: []
summary: Claim unassigned task
tags:
- tasks
/tasks/{id}/drop:
post:
parameters:
- description: Request id
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/Request'
"404":
description: Not Found
schema:
$ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError'
"409":
description: Conflict
schema:
$ref: '#/definitions/github_com_generate_selfserve_internal_errs.HTTPError'
security:
- BearerAuth: []
summary: Drop claimed task back to pool
tags:
- tasks
/users:
post:
consumes:
Expand Down
5 changes: 5 additions & 0 deletions backend/internal/errs/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ func Conflict(title string, withKey string, withValue any) HTTPError {
return NewHTTPError(http.StatusConflict, fmt.Errorf("conflict: %s with %s='%s' already exists", title, withKey, withValue))
}

// TaskStateHTTPConflict is returned when claim/drop or similar task preconditions fail.
func TaskStateHTTPConflict() HTTPError {
return NewHTTPError(http.StatusConflict, errors.New("task state conflict"))
}

func InvalidRequestData(errors map[string]string) HTTPError {
return HTTPError{
Code: http.StatusUnprocessableEntity,
Expand Down
9 changes: 7 additions & 2 deletions backend/internal/errs/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import "errors"

// Repository layer errors (errors thrown at the DB level)
var (
ErrNotFoundInDB = errors.New("not found in DB")
ErrAlreadyExistsInDB = errors.New("already exists in DB")
ErrNotFoundInDB = errors.New("not found in DB")
ErrAlreadyExistsInDB = errors.New("already exists in DB")

ErrTaskStateConflict = errors.New("task state conflict")
ErrRequestUnknownHotel = errors.New("unknown hotel for request")
ErrRequestUnknownAssignee = errors.New("unknown assignee for request")
ErrRequestInvalidUserID = errors.New("invalid user id for request")
ErrDefaultDepartmentInsertDB = errors.New("failed to insert default departments")
)
38 changes: 38 additions & 0 deletions backend/internal/handler/auth_context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package handler

import (
"context"
"errors"
"strings"

"github.com/generate/selfserve/internal/errs"
"github.com/generate/selfserve/internal/models"
"github.com/gofiber/fiber/v2"
)

type authUserLookup interface {
FindUser(ctx context.Context, id string) (*models.User, error)
}

// userIDAndHotelFromAuth resolves the Clerk subject from JWT locals and loads the user's hotel_id.
func userIDAndHotelFromAuth(c *fiber.Ctx, users authUserLookup) (clerkID, hotelID string, err error) {
raw := c.Locals("userId")
clerkID, _ = raw.(string)
if strings.TrimSpace(clerkID) == "" {
return "", "", errs.Unauthorized()
}

u, ferr := users.FindUser(c.Context(), clerkID)
if ferr != nil {
if errors.Is(ferr, errs.ErrNotFoundInDB) {
return "", "", errs.BadRequest("user is not registered; complete sign-up first")
}
return "", "", errs.InternalServerError()
}

if strings.TrimSpace(u.HotelID) == "" {
return "", "", errs.BadRequest("user has no hotel assigned")
}

return clerkID, u.HotelID, nil
}
3 changes: 1 addition & 2 deletions backend/internal/handler/hotels_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,13 @@ func TestHotelHandler_GetHotelByID(t *testing.T) {
func TestHotelsHandler_CreateHotel(t *testing.T) {
t.Parallel()

floors := 10
validBody := `{
"id": "org_2abc123",
"name": "The Grand Budapest Hotel",
"floors": 10
}`

newMock := func(returnFloors *int) *mockHotelsRepository {
newMock := func() *mockHotelsRepository {
return &mockHotelsRepository{
insertHotelFunc: func(ctx context.Context, hotel *models.CreateHotelRequest) (*models.Hotel, error) {
return &models.Hotel{
Expand Down
Loading
Loading