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
10 changes: 2 additions & 8 deletions backend/cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,8 @@ func main() {
}

defer func() {
if err := app.Repo.Close(); err != nil {
panic(fmt.Sprintf("failed to close repo: %v", err))
}
if app.TemporalWorker != nil {
app.TemporalWorker.Stop()
}
if app.TemporalClient != nil {
app.TemporalClient.Close()
if err := app.Close(); err != nil {
panic(fmt.Sprintf("failed to close app resources: %v", err))
}
}()

Expand Down
1 change: 1 addition & 0 deletions backend/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ type Config struct {
LLM `env:",prefix=LLM_"`
Temporal `env:",prefix=TEMPORAL_"`
Clerk `env:",prefix=CLERK_"`
Redis `env:",prefix=REDIS_"`
OpenSearch `env:",prefix=OPENSEARCH_"`
}
11 changes: 11 additions & 0 deletions backend/config/redis.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package config

import "time"

type Redis struct {
Enabled bool `env:"ENABLED,default=false"`
Addr string `env:"ADDR,default=localhost:6379"`
Password string `env:"PASSWORD"`
DB int `env:"DB,default=0"`
PingTimeout time.Duration `env:"PING_TIMEOUT,default=1s"`
}
25 changes: 25 additions & 0 deletions backend/config/redis_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package config

import (
"context"
"testing"
"time"

"github.com/sethvargo/go-envconfig"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestRedisConfigDefaults(t *testing.T) {
t.Parallel()

var cfg Redis
err := envconfig.Process(context.Background(), &cfg)

require.NoError(t, err)
assert.False(t, cfg.Enabled)
assert.Equal(t, "localhost:6379", cfg.Addr)
assert.Equal(t, "", cfg.Password)
assert.Equal(t, 0, cfg.DB)
assert.Equal(t, time.Second, cfg.PingTimeout)
}
47 changes: 47 additions & 0 deletions backend/internal/cache/domain/guest_bookings.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package domain

import (
"context"
"time"

"github.com/generate/selfserve/internal/cache/object"
storage "github.com/generate/selfserve/internal/service/storage/postgres"
)

type CachedGuestBookingsRepository struct {
cache *object.Cache
next storage.GuestBookingsRepository
}

func NewCachedGuestBookingsRepository(cache *object.Cache, next storage.GuestBookingsRepository) *CachedGuestBookingsRepository {
return &CachedGuestBookingsRepository{cache: cache, next: next}
}

func (r *CachedGuestBookingsRepository) FindGroupSizeOptions(ctx context.Context, hotelID string) ([]int, error) {
if r.cache == nil {
return r.next.FindGroupSizeOptions(ctx, hotelID)
}

key := guestBookingGroupSizesKey(hotelID)
var cached []int
if hit, err := r.cache.Get(ctx, key, &cached); err != nil {
r.cache.WarnReadError(key, err)
} else if hit {
return cached, nil
}

sizes, err := r.next.FindGroupSizeOptions(ctx, hotelID)
if err != nil {
return nil, err
}

if err := r.cache.Set(ctx, key, sizes, guestBookingGroupSizesTTL); err != nil {
r.cache.WarnWriteError(key, err)
}
return sizes, nil
}

func (r *CachedGuestBookingsRepository) InsertGuestBooking(ctx context.Context, guestID, roomID, hotelID string, arrivalDate, departureDate time.Time) error {
// Future guest booking writes should invalidate hotel group sizes and affected guest stay history.
return r.next.InsertGuestBooking(ctx, guestID, roomID, hotelID, arrivalDate, departureDate)
}
100 changes: 100 additions & 0 deletions backend/internal/cache/domain/guests.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package domain

import (
"context"

"github.com/generate/selfserve/internal/cache/object"
"github.com/generate/selfserve/internal/models"
storage "github.com/generate/selfserve/internal/service/storage/postgres"
)

type CachedGuestsRepository struct {
cache *object.Cache
next storage.GuestsRepository
}

func NewCachedGuestsRepository(cache *object.Cache, next storage.GuestsRepository) *CachedGuestsRepository {
return &CachedGuestsRepository{cache: cache, next: next}
}

func (r *CachedGuestsRepository) InsertGuest(ctx context.Context, guest *models.CreateGuest) (*models.Guest, error) {
created, err := r.next.InsertGuest(ctx, guest)
if err != nil {
return nil, err
}
r.invalidateGuest(ctx, created.ID)
return created, nil
}

func (r *CachedGuestsRepository) FindGuest(ctx context.Context, id string) (*models.Guest, error) {
if r.cache == nil {
return r.next.FindGuest(ctx, id)
}

key := guestKey(id)
var cached models.Guest
if hit, err := r.cache.Get(ctx, key, &cached); err != nil {
r.cache.WarnReadError(key, err)
} else if hit {
return &cached, nil
}

guest, err := r.next.FindGuest(ctx, id)
if err != nil {
return nil, err
}

if err := r.cache.Set(ctx, key, guest, guestTTL); err != nil {
r.cache.WarnWriteError(key, err)
}
return guest, nil
}

func (r *CachedGuestsRepository) UpdateGuest(ctx context.Context, id string, update *models.UpdateGuest) (*models.Guest, error) {
updated, err := r.next.UpdateGuest(ctx, id, update)
if err != nil {
return nil, err
}
r.invalidateGuest(ctx, id)
return updated, nil
}

func (r *CachedGuestsRepository) FindGuestsWithActiveBooking(ctx context.Context, filters *models.GuestFilters) (*models.GuestPage, error) {
return r.next.FindGuestsWithActiveBooking(ctx, filters)
}

func (r *CachedGuestsRepository) FindGuestWithStayHistory(ctx context.Context, id string) (*models.GuestWithStays, error) {
if r.cache == nil {
return r.next.FindGuestWithStayHistory(ctx, id)
}

key := guestStayHistoryKey(id)
var cached models.GuestWithStays
if hit, err := r.cache.Get(ctx, key, &cached); err != nil {
r.cache.WarnReadError(key, err)
} else if hit {
return &cached, nil
}

guest, err := r.next.FindGuestWithStayHistory(ctx, id)
if err != nil {
return nil, err
}

if err := r.cache.Set(ctx, key, guest, guestStayHistoryTTL); err != nil {
r.cache.WarnWriteError(key, err)
}
return guest, nil
}

func (r *CachedGuestsRepository) invalidateGuest(ctx context.Context, id string) {
if r.cache == nil || id == "" {
return
}

for _, key := range []string{guestKey(id), guestStayHistoryKey(id)} {
if err := r.cache.Delete(ctx, key); err != nil {
r.cache.WarnDeleteError(key, err)
}
}
}
62 changes: 62 additions & 0 deletions backend/internal/cache/domain/hotels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package domain

import (
"context"

"github.com/generate/selfserve/internal/cache/object"
"github.com/generate/selfserve/internal/models"
storage "github.com/generate/selfserve/internal/service/storage/postgres"
)

type CachedHotelsRepository struct {
cache *object.Cache
next storage.HotelsRepository
}

func NewCachedHotelsRepository(cache *object.Cache, next storage.HotelsRepository) *CachedHotelsRepository {
return &CachedHotelsRepository{cache: cache, next: next}
}

func (r *CachedHotelsRepository) FindByID(ctx context.Context, id string) (*models.Hotel, error) {
if r.cache == nil {
return r.next.FindByID(ctx, id)
}

key := hotelKey(id)
var cached models.Hotel
if hit, err := r.cache.Get(ctx, key, &cached); err != nil {
r.cache.WarnReadError(key, err)
} else if hit {
return &cached, nil
}

hotel, err := r.next.FindByID(ctx, id)
if err != nil {
return nil, err
}

if err := r.cache.Set(ctx, key, hotel, hotelTTL); err != nil {
r.cache.WarnWriteError(key, err)
}
return hotel, nil
}

func (r *CachedHotelsRepository) InsertHotel(ctx context.Context, hotel *models.CreateHotelRequest) (*models.Hotel, error) {
return r.next.InsertHotel(ctx, hotel)
}

func (r *CachedHotelsRepository) GetDepartmentsByHotelID(ctx context.Context, hotelID string) ([]*models.Department, error) {
return r.next.GetDepartmentsByHotelID(ctx, hotelID)
}

func (r *CachedHotelsRepository) InsertDepartment(ctx context.Context, hotelID, name string) (*models.Department, error) {
return r.next.InsertDepartment(ctx, hotelID, name)
}

func (r *CachedHotelsRepository) UpdateDepartment(ctx context.Context, id, hotelID, name string) (*models.Department, error) {
return r.next.UpdateDepartment(ctx, id, hotelID, name)
}

func (r *CachedHotelsRepository) DeleteDepartment(ctx context.Context, id, hotelID string) error {
return r.next.DeleteDepartment(ctx, id, hotelID)
}
23 changes: 23 additions & 0 deletions backend/internal/cache/domain/keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package domain

const keyPrefix = "selfserve:v1"

func userKey(id string) string {
return keyPrefix + ":users:" + id
}

func hotelKey(id string) string {
return keyPrefix + ":hotels:" + id
}

func guestKey(id string) string {
return keyPrefix + ":guests:" + id
}

func guestStayHistoryKey(id string) string {
return keyPrefix + ":guests:" + id + ":stay_history"
}

func guestBookingGroupSizesKey(hotelID string) string {
return keyPrefix + ":hotels:" + hotelID + ":guest_booking_group_sizes"
}
11 changes: 11 additions & 0 deletions backend/internal/cache/domain/policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package domain

import "time"

const (
userTTL = 5 * time.Minute
hotelTTL = 15 * time.Minute
guestTTL = 2 * time.Minute
guestStayHistoryTTL = 1 * time.Minute
guestBookingGroupSizesTTL = 5 * time.Minute
)
Loading
Loading