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: 10 additions & 0 deletions cmd/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
Expand Down Expand Up @@ -74,6 +75,15 @@ func main() {
}))
http.HandleFunc("/sessions", enableCORS(sessionHandler.HandleCreateRoom))
http.HandleFunc("/ws", wsHandler.HandleConnection)
http.HandleFunc("/tags", enableCORS(func(w http.ResponseWriter, r *http.Request) {
tags, err := restaurantRepo.GetUniqueCuisines(context.Background())
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(tags)
}))

log.Fatal(http.ListenAndServe(":"+port, nil))
}
5 changes: 4 additions & 1 deletion deploy/database/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,7 @@ ADD COLUMN IF NOT EXISTS rating NUMERIC(3, 2),
ADD COLUMN IF NOT EXISTS user_ratings_total INT,
ADD COLUMN IF NOT EXISTS price_level INT,
ADD COLUMN IF NOT EXISTS photo_reference TEXT,
ADD COLUMN IF NOT EXISTS formatted_address TEXT;
ADD COLUMN IF NOT EXISTS formatted_address TEXT;

ALTER TABLE restaurants
ADD COLUMN IF NOT EXISTS opening_hours TEXT[] DEFAULT '{}';
20 changes: 17 additions & 3 deletions internal/application/create_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ type CreateSessionInput struct {
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
RadiusMeters int `json:"radius_meters"`

PriceTiers []int `json:"price_tiers"`
MinRating *float64 `json:"min_rating"`
Cuisines []string `json:"cuisines"`
}

type CreateSessionUseCase struct {
Expand All @@ -39,7 +43,7 @@ func (uc *CreateSessionUseCase) Execute(ctx context.Context, input CreateSession
return nil, errors.New("radius must be greater than zero")
}

restaurants, err := uc.restaurantRepo.GetByLocation(ctx, input.Latitude, input.Longitude, input.RadiusMeters)
restaurants, err := uc.restaurantRepo.GetByLocation(ctx, input.Latitude, input.Longitude, input.RadiusMeters, input.PriceTiers, input.MinRating, input.Cuisines)
if err != nil {
return nil, err
}
Expand All @@ -49,7 +53,7 @@ func (uc *CreateSessionUseCase) Execute(ctx context.Context, input CreateSession
if err != nil {
log.Printf("Warning: OSM Fetch failed: %v", err)
}
restaurants, err = uc.restaurantRepo.GetByLocation(ctx, input.Latitude, input.Longitude, input.RadiusMeters)
restaurants, err = uc.restaurantRepo.GetByLocation(ctx, input.Latitude, input.Longitude, input.RadiusMeters, input.PriceTiers, input.MinRating, input.Cuisines)
if err != nil {
return nil, err
}
Expand All @@ -65,6 +69,11 @@ func (uc *CreateSessionUseCase) Execute(ctx context.Context, input CreateSession
}
sessionID := hex.EncodeToString(bytes)

minRatingVal := 0.0
if input.MinRating != nil {
minRatingVal = *input.MinRating
}

session := &domain.Session{
ID: sessionID,
HostID: input.HostID,
Expand All @@ -73,7 +82,12 @@ func (uc *CreateSessionUseCase) Execute(ctx context.Context, input CreateSession
Participants: []domain.Participant{
{ID: input.HostID, Username: "Host"},
},
Pool: restaurants,
Pool: restaurants,
Filters: domain.SessionFilters{
PriceTiers: input.PriceTiers,
MinRating: minRatingVal,
Cuisines: input.Cuisines,
},
CreatedAt: time.Now(),
}

Expand Down
3 changes: 2 additions & 1 deletion internal/application/enrich_restaurants.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (uc *EnrichRestaurantsUseCase) ExecuteAsynchronously(session *domain.Sessio
}

_ = uc.restaurantRepo.UpdateGooglePlacesData(
ctx, rest.ID, &details.ID, details.Rating, details.UserRatingsTotal, details.PriceLevel, details.PhotoReference, details.FormattedAddress, details.Tags,
ctx, rest.ID, &details.ID, details.Rating, details.UserRatingsTotal, details.PriceLevel, details.PhotoReference, details.FormattedAddress, details.Tags, details.OpeningHours,
)

session.Pool[index].GooglePlaceID = &details.ID
Expand All @@ -63,6 +63,7 @@ func (uc *EnrichRestaurantsUseCase) ExecuteAsynchronously(session *domain.Sessio
session.Pool[index].PhotoReference = details.PhotoReference
session.Pool[index].FormattedAddress = details.FormattedAddress
session.Pool[index].CuisineTags = append(session.Pool[index].CuisineTags, details.Tags...)
session.Pool[index].OpeningHours = details.OpeningHours

if broadcaster != nil {
broadcaster.Broadcast(session.ID, map[string]interface{}{
Expand Down
5 changes: 3 additions & 2 deletions internal/application/ports.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import (
)

type RestaurantRepository interface {
GetByLocation(ctx context.Context, lat, lon float64, radiusMeters int) ([]domain.Restaurant, error)
GetByLocation(ctx context.Context, lat, lon float64, radiusMeters int, priceTiers []int, minRating *float64, cuisines []string) ([]domain.Restaurant, error)
FetchAndSaveFromOSM(ctx context.Context, lat, lon float64, radiusMeters int) error
UpdateGooglePlacesData(ctx context.Context, id string, googlePlaceID *string, rating *float64, userRatingsTotal *int, priceLevel *int, photoReference *string, formattedAddress *string, extraTags []string) error
UpdateGooglePlacesData(ctx context.Context, id string, googlePlaceID *string, rating *float64, userRatingsTotal *int, priceLevel *int, photoReference *string, formattedAddress *string, extraTags []string, openingHours []string) error
GetUniqueCuisines(ctx context.Context) ([]string, error)
}

type SessionRepository interface {
Expand Down
1 change: 1 addition & 0 deletions internal/domain/restaurant.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ type Restaurant struct {
PriceLevel *int `json:"price_level,omitempty"`
PhotoReference *string `json:"photo_reference,omitempty"`
FormattedAddress *string `json:"formatted_address,omitempty"`
OpeningHours []string `json:"opening_hours,omitempty"`
}
7 changes: 7 additions & 0 deletions internal/domain/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ type Participant struct {
Username string `json:"username"`
}

type SessionFilters struct {
PriceTiers []int `json:"price_tiers"`
MinRating float64 `json:"min_rating"`
Cuisines []string `json:"cuisines"`
}

type Session struct {
ID string `json:"id"`
HostID string `json:"host_id"`
Expand All @@ -37,6 +43,7 @@ type Session struct {
MatchedID string `json:"matched_id,omitempty"`
Votes map[string]map[string]VoteType `json:"votes"`
TiedIDs []string `json:"tied_ids,omitempty"`
Filters SessionFilters `json:"filters"`
CreatedAt time.Time `json:"created_at"`
}

Expand Down
62 changes: 57 additions & 5 deletions internal/infrastructure/database/postgres_restaurant_repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,44 @@ func NewPostgresRestaurantRepo(db *pgxpool.Pool) *PostgresRestaurantRepo {
}
}

func (r *PostgresRestaurantRepo) GetByLocation(ctx context.Context, lat, lon float64, radiusMeters int) ([]domain.Restaurant, error) {
func (r *PostgresRestaurantRepo) GetByLocation(ctx context.Context, lat, lon float64, radiusMeters int, priceTiers []int, minRating *float64, cuisines []string) ([]domain.Restaurant, error) {
query := `
SELECT id, osm_id, name, ST_Y(location::geometry) as lat, ST_X(location::geometry) as lon, cuisine_tags, google_place_id, rating, user_ratings_total, price_level, photo_reference, formatted_address
SELECT id, osm_id, name, ST_Y(location::geometry) as lat, ST_X(location::geometry) as lon, cuisine_tags, google_place_id, rating, user_ratings_total, price_level, photo_reference, formatted_address, opening_hours
FROM restaurants
WHERE ST_DWithin(
location,
ST_SetSRID(ST_MakePoint($1, $2), 4326)::geography,
$3
)
LIMIT 30;
`

rows, err := r.db.Query(ctx, query, lon, lat, radiusMeters)
args := []interface{}{lon, lat, radiusMeters}
argCount := 3

if len(priceTiers) > 0 {
argCount++
query += fmt.Sprintf(" AND (price_level = ANY($%d) OR price_tier = ANY($%d) OR price_level IS NULL)", argCount, argCount)
args = append(args, priceTiers)
}

if minRating != nil && *minRating > 0 {
argCount++
query += fmt.Sprintf(" AND (rating >= $%d OR rating IS NULL)", argCount)
args = append(args, *minRating)
}

if len(cuisines) > 0 {
argCount++
query += fmt.Sprintf(` AND EXISTS (
SELECT 1 FROM unnest(cuisine_tags) db_tag, unnest($%d::text[]) filter_tag
WHERE db_tag ILIKE '%%' || filter_tag || '%%'
)`, argCount)
args = append(args, cuisines)
}

query += " LIMIT 30;"

rows, err := r.db.Query(ctx, query, args...)
if err != nil {
return nil, fmt.Errorf("failed to query restaurants: %w", err)
}
Expand All @@ -68,6 +93,7 @@ func (r *PostgresRestaurantRepo) GetByLocation(ctx context.Context, lat, lon flo
&rest.PriceLevel,
&rest.PhotoReference,
&rest.FormattedAddress,
&rest.OpeningHours,
); err != nil {
return nil, fmt.Errorf("failed to scan restaurant row: %w", err)
}
Expand Down Expand Up @@ -151,7 +177,7 @@ func (r *PostgresRestaurantRepo) FetchAndSaveFromOSM(ctx context.Context, lat, l
return nil
}

func (r *PostgresRestaurantRepo) UpdateGooglePlacesData(ctx context.Context, id string, googlePlaceID *string, rating *float64, userRatingsTotal *int, priceLevel *int, photoReference *string, formattedAddress *string, extraTags []string) error {
func (r *PostgresRestaurantRepo) UpdateGooglePlacesData(ctx context.Context, id string, googlePlaceID *string, rating *float64, userRatingsTotal *int, priceLevel *int, photoReference *string, formattedAddress *string, extraTags []string, openingHours []string) error {
query := `
UPDATE restaurants
SET google_place_id = $2,
Expand All @@ -161,6 +187,7 @@ func (r *PostgresRestaurantRepo) UpdateGooglePlacesData(ctx context.Context, id
photo_reference = $6,
formatted_address = $7
cuisine_tags = array_cat(cuisine_tags, $8)
opening_hours = $9
WHERE id = $1
`

Expand All @@ -175,7 +202,32 @@ func (r *PostgresRestaurantRepo) UpdateGooglePlacesData(ctx context.Context, id
photoReference,
formattedAddress,
extraTags,
openingHours,
)

return err
}

func (r *PostgresRestaurantRepo) GetUniqueCuisines(ctx context.Context) ([]string, error) {
query := `
SELECT DISTINCT initcap(replace(tag, '_', ' '))
FROM (SELECT unnest(cuisine_tags) AS tag FROM restaurants) sub
WHERE tag != ''
ORDER BY 1
LIMIT 50;
`
rows, err := r.db.Query(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()

tags := []string{}
for rows.Next() {
var tag string
if err := rows.Scan(&tag); err == nil {
tags = append(tags, tag)
}
}
return tags, nil
}
27 changes: 18 additions & 9 deletions internal/infrastructure/places/google_places_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type GooglePlaceResult struct {
PhotoReference *string
FormattedAddress *string
Tags []string
OpeningHours []string
}

type GooglePlacesClient struct {
Expand Down Expand Up @@ -54,19 +55,24 @@ type searchTextResponse struct {
}

type place struct {
Id string `json:"id"`
Rating float64 `json:"rating"`
UserRatingCount int `json:"userRatingCount"`
PriceLevel string `json:"priceLevel"`
FormattedAddress string `json:"formattedAddress"`
Types []string `json:"types"`
Photos []photo `json:"photos"`
Id string `json:"id"`
Rating float64 `json:"rating"`
UserRatingCount int `json:"userRatingCount"`
PriceLevel string `json:"priceLevel"`
FormattedAddress string `json:"formattedAddress"`
Types []string `json:"types"`
Photos []photo `json:"photos"`
RegularOpeningHours regularOpeningHours `json:"regularOpeningHours"`
}

type photo struct {
Name string `json:"name"`
}

type regularOpeningHours struct {
WeekdayDescriptions []string `json:"weekdayDescriptions"`
}

func (c *GooglePlacesClient) FetchRestaurantDetails(ctx context.Context, name string, lat, lon float64) (*GooglePlaceResult, error) {
if c.apiKey == "" {
return nil, fmt.Errorf("GOOGLE_PLACES_API_KEY is not set")
Expand Down Expand Up @@ -106,8 +112,7 @@ func (c *GooglePlacesClient) FetchRestaurantDetails(ctx context.Context, name st
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Goog-Api-Key", c.apiKey)

req.Header.Set("X-Goog-FieldMask", "places.id,places.rating,places.userRatingCount,places.priceLevel,places.photos,places.formattedAddress,places.types")

req.Header.Set("X-Goog-FieldMask", "places.id,places.rating,places.userRatingCount,places.priceLevel,places.photos,places.formattedAddress,places.types,places.regularOpeningHours.weekdayDescriptions")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
Expand Down Expand Up @@ -163,5 +168,9 @@ func (c *GooglePlacesClient) FetchRestaurantDetails(ctx context.Context, name st
}
}

if len(p.RegularOpeningHours.WeekdayDescriptions) > 0 {
result.OpeningHours = p.RegularOpeningHours.WeekdayDescriptions
}

return result, nil
}
Loading