package db import ( "context" "fmt" "strings" "github.com/henvic/pgq" "github.com/jackc/pgx/v5/pgtype" ) type GetTopListLocationsParams struct { Limit int32 Offset int32 OrderBy string RegionType pgtype.Int4 } type GetTopListLocationsRow struct { RowNumber int32 `json:"row_number"` ID int32 `json:"id"` Name string `json:"name"` RegionName string `json:"region_name"` Thumbnail pgtype.Text `json:"thumbnail"` Address string `json:"address"` GoogleMapsLink string `json:"google_maps_link"` RegencyName string `json:"regency_name"` CriticScore int16 `json:"critic_score"` CriticCount int16 `json:"critic_count"` UserScore int16 `json:"user_score"` UserCount int16 `json:"user_count"` TotalCount int16 `json:"total_count"` CriticBayes int16 `json:"critic_bayes"` UserBayes int16 `json:"user_bayes"` AvgBayes int16 `json:"avg_bayes"` } func (q *Queries) GetTopListLocations(ctx context.Context, arg GetTopListLocationsParams) ([]GetTopListLocationsRow, error) { regionType := "" if arg.RegionType.Valid { regionType = fmt.Sprintf("AND reg.id = %d", arg.RegionType.Int32) } // https://fulmicoton.com/posts/bayesian_rating/ getTopListQ := fmt.Sprintf(`SELECT row_number() over (ORDER BY %s DESC) as row_number, * FROM( SELECT *, (SELECT 5 * 5 + COALESCE(critic_score, 0) * COALESCE(critic_count, 0) / 5 + COALESCE(critic_count, 0)) as critic_bayes, (SELECT 5 * 5 + COALESCE(user_score, 0) * COALESCE(user_count, 0) / 5 + COALESCE(user_count, 0)) as user_bayes, ((SELECT 5 * 5 + COALESCE(user_score, 0) * COALESCE(user_count, 0) / 50 + COALESCE(user_count, 0)) + (SELECT 5 * 5 + COALESCE(critic_score, 0) * COALESCE(critic_count, 0) / 5 + COALESCE(critic_count, 0)) ) / 2 as avg_bayes FROM ( SELECT l.id, name, l.address, reg.region_name as region_name, l.google_maps_link, thumbnail, re.regency_name, (SELECT COALESCE(SUM(score), 0) from reviews re where re.is_from_critic = true and re.location_id = l.id) as critic_score, (SELECT COUNT(id) from reviews re where re.is_from_critic = true and re.location_id = l.id) as critic_count, (SELECT COALESCE(SUM(score), 0) from reviews re where re.is_from_critic = false and re.location_id = l.id) as user_score, (SELECT COUNT(id) from reviews re where re.is_from_critic = false and re.location_id = l.id) as user_count FROM locations l JOIN regencies re on re.id = l.regency_id JOIN provinces prov on prov.id = re.province_id JOIN regions reg on reg.id = prov.region_id WHERE approved_by IS NOT NULL %s ) iq1 ) iq2 LIMIT $1 OFFSET $2;`, arg.OrderBy, regionType) rows, err := q.db.Query(ctx, getTopListQ, arg.Limit, arg.Offset) if err != nil { return nil, err } defer rows.Close() items := []GetTopListLocationsRow{} for rows.Next() { var i GetTopListLocationsRow if err := rows.Scan( &i.RowNumber, &i.ID, &i.Name, &i.Address, &i.RegionName, &i.GoogleMapsLink, &i.Thumbnail, &i.RegencyName, &i.CriticScore, &i.CriticCount, &i.UserScore, &i.UserCount, &i.CriticBayes, &i.UserBayes, &i.AvgBayes, ); err != nil { return nil, err } i.TotalCount = i.UserCount + i.CriticCount items = append(items, i) } rows.Close() if err := rows.Err(); err != nil { return nil, err } return items, nil } type GetCountLocationsParams struct { LocationTypes string `json:"location_type"` Regency string `json:"regency"` } func (q *Queries) GetCountLocations(ctx context.Context, arg GetCountLocationsParams) (int32, error) { var count int32 sqlBuilder := pgq.Select("COUNT(id)").From("locations l") if arg.Regency != "" { regencies := strings.Split(arg.Regency, ",") sqlBuilder = sqlBuilder.Where(pgq.Eq{"regency_id": regencies}) } if arg.LocationTypes != "" { locType := strings.Split(arg.LocationTypes, ",") sqlBuilder = sqlBuilder.Where(pgq.Eq{"l.location_type": locType}) } query, argx, err := sqlBuilder.SQL() if err != nil { return 0, err } err = q.db.QueryRow(ctx, query, argx...).Scan(&count) if err != nil { return 0, err } return count, err } type GetListRecentLocationsWithRatingsRow struct { ID int32 `json:"id"` Name string `json:"name"` Thumbnail pgtype.Text `json:"thumbnail"` LocationType pgtype.Text `json:"location_type"` RegencyName string `json:"regency_name"` ProvinceName string `json:"province_name"` CriticScore interface{} `json:"critic_score"` CriticCount int64 `json:"critic_count"` UserScore interface{} `json:"user_score"` UserCount int64 `json:"user_count"` } type GetListRecentLocationsParams struct { Limit uint64 `json:"limit" default:"15"` Regions string `json:"regions" default:""` LocationTypes string `json:"location_type" default:""` Offset uint64 `json:"offset" default:"0"` } func (q *Queries) GetListRecentLocationsWithRatings(ctx context.Context, arg GetListRecentLocationsParams) ([]GetListRecentLocationsWithRatingsRow, error) { sqlBuilder := pgq.Select( "l.id", "name", "thumbnail", "l.location_type", "COALESCE(re.regency_name, '') as regency_name", "COALESCE(pr.province_name, '') as province_name", "(SELECT COALESCE(SUM(score), 0) from reviews re where re.is_from_critic = true and re.location_id = l.id) as critic_score", "(SELECT COUNT(id) from reviews re where re.is_from_critic = true and re.location_id = l.id) as critic_count", "(SELECT COALESCE(SUM(score), 0) from reviews re where re.is_from_critic = false and re.location_id = l.id) as user_score", "(SELECT COUNT(id) from reviews re where re.is_from_critic = false and re.location_id = l.id) as user_count", ). From("locations l"). Join("regencies re on re.id = l.regency_id"). Join("provinces pr on re.province_id = pr.id"). Limit(arg.Limit). Offset(arg.Offset) if arg.Regions != "" { region := strings.Split(arg.Regions, ",") sqlBuilder = sqlBuilder.Where(pgq.Eq{"re.id": region}) } if arg.LocationTypes != "" { locType := strings.Split(arg.LocationTypes, ",") sqlBuilder = sqlBuilder.Where(pgq.Eq{"l.location_type": locType}) } query, arguments, err := sqlBuilder.Where(pgq.NotEq{"approved_by": nil}).SQL() if err != nil { return nil, err } rows, err := q.db.Query(ctx, query, arguments...) if err != nil { return nil, err } defer rows.Close() items := []GetListRecentLocationsWithRatingsRow{} for rows.Next() { var i GetListRecentLocationsWithRatingsRow if err := rows.Scan( &i.ID, &i.Name, &i.Thumbnail, &i.LocationType, &i.RegencyName, &i.ProvinceName, &i.CriticScore, &i.CriticCount, &i.UserScore, &i.UserCount, ); err != nil { return nil, err } items = append(items, i) } if err := rows.Err(); err != nil { return nil, err } return items, nil } type GetLocationRow struct { ID int32 `json:"id"` Name string `json:"name"` Address string `json:"address"` RegencyName string `json:"regency_name"` ProvinceName string `json:"province_name"` RegionName string `json:"region_name"` GoogleMapsLink string `json:"google_maps_link"` Thumbnail pgtype.Text `json:"thumbnail"` SubmittedBy string `json:"submitted_by"` CriticScore int32 `json:"critic_score"` CriticCount int32 `json:"critic_count"` UserScore int32 `json:"user_score"` UserCount int32 `json:"user_count"` } var getLocationQ = ` SELECT l.id, name, l.address, COALESCE(re.regency_name, '') as regency_name, COALESCE(prov.province_name, '') as province_name, COALESCE(reg.region_name, '') as region_name, COALESCE(l.google_maps_link, '') as google_maps_link, thumbnail, u.username as submitted_by, (SELECT COALESCE(SUM(score), 0) from reviews re where re.is_from_critic = true and re.location_id = l.id) as critic_score, (SELECT COUNT(id) from reviews re where re.is_from_critic = true and re.location_id = l.id) as critic_count, (SELECT COALESCE(SUM(score), 0) from reviews re where re.is_from_critic = false and re.location_id = l.id) as user_score, (SELECT COUNT(id) from reviews re where re.is_from_critic = false and re.location_id = l.id) as user_count FROM locations l JOIN regencies re on re.id = l.regency_id JOIN provinces prov on prov.id = re.province_id JOIN regions reg on reg.id = prov.region_id JOIN users u on u.id = l.submitted_by WHERE l.id = $1 ` func (q *Queries) GetLocation(ctx context.Context, location_id int32) (GetLocationRow, error) { row := q.db.QueryRow(ctx, getLocationQ, location_id) var i GetLocationRow err := row.Scan( &i.ID, &i.Name, &i.Address, &i.RegencyName, &i.ProvinceName, &i.RegionName, &i.GoogleMapsLink, &i.Thumbnail, &i.SubmittedBy, &i.CriticScore, &i.CriticCount, &i.UserScore, &i.UserCount, ) return i, err } const createLocation = `-- name: CreateLocation :exec INSERT INTO locations( address, name, submitted_by, location_type, regency_id, google_maps_link, approved_by, is_deleted ) values ( $1, $2, $3, $4, $5, $6, $7, $8 ) RETURNING id ` type CreateLocationParams struct { Address string `json:"address"` Name string `json:"name"` SubmittedBy int32 `json:"submitted_by"` LocationType LocationType `json:"location_type"` RegencyID int16 `json:"regency_id"` GoogleMapsLink pgtype.Text `json:"google_maps_link"` IsDeleted bool `json:"is_deleted"` ApprovedBy pgtype.Int4 `json:"approved_by"` } func (q *Queries) CreateLocation(ctx context.Context, arg CreateLocationParams) (int32, error) { row := q.db.QueryRow(ctx, createLocation, arg.Address, arg.Name, arg.SubmittedBy, arg.LocationType, arg.RegencyID, arg.GoogleMapsLink, arg.ApprovedBy, arg.IsDeleted, ) var i int32 err := row.Scan( &i, ) fmt.Println(i) return i, err }