285 lines
8.3 KiB
Go
285 lines
8.3 KiB
Go
package sites
|
|
|
|
import (
|
|
"context"
|
|
"furtuna-be/internal/common/logger"
|
|
"furtuna-be/internal/common/mycontext"
|
|
"furtuna-be/internal/entity"
|
|
|
|
"go.uber.org/zap"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type SiteRepository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewSiteRepository(db *gorm.DB) *SiteRepository {
|
|
return &SiteRepository{
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
func (r *SiteRepository) Upsert(ctx context.Context, site *entity.Site) (*entity.Site, error) {
|
|
err := r.db.Transaction(func(tx *gorm.DB) error {
|
|
if site.ID != 0 {
|
|
// Update site
|
|
if err := tx.Save(site).Error; err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// Create new site
|
|
if err := tx.Create(site).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(site.Products) > 0 {
|
|
for i := range site.Products {
|
|
site.Products[i].SiteID = site.ID
|
|
if site.Products[i].ID != 0 {
|
|
// Update existing product
|
|
if err := tx.Save(&site.Products[i]).Error; err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
// Create new product
|
|
if err := tx.Create(&site.Products[i]).Error; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Error("error when upserting site", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
return site, nil
|
|
}
|
|
|
|
func (r *SiteRepository) Create(ctx context.Context, site *entity.SiteDB) (*entity.SiteDB, error) {
|
|
err := r.db.Create(site).Error
|
|
if err != nil {
|
|
logger.ContextLogger(ctx).Error("error when creating site", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
return site, nil
|
|
}
|
|
|
|
func (r *SiteRepository) Update(ctx context.Context, site *entity.SiteDB) (*entity.SiteDB, error) {
|
|
if err := r.db.Save(site).Error; err != nil {
|
|
logger.ContextLogger(ctx).Error("error when updating site", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
return site, nil
|
|
}
|
|
|
|
func (r *SiteRepository) GetByID(ctx context.Context, id int64) (*entity.SiteDB, error) {
|
|
site := new(entity.SiteDB)
|
|
if err := r.db.Preload("Products").First(site, id).Error; err != nil {
|
|
logger.ContextLogger(ctx).Error("error when getting site by ID", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
return site, nil
|
|
}
|
|
|
|
func (r *SiteRepository) GetAll(ctx context.Context, req entity.SiteSearch) (entity.SiteList, int, error) {
|
|
var sites []*entity.SiteDB
|
|
var total int64
|
|
|
|
query := r.db
|
|
query = query.Where("deleted_at IS NULL")
|
|
|
|
if req.Status != "" {
|
|
query = query.Where("status = ?", req.Status)
|
|
}
|
|
|
|
if req.Search != "" {
|
|
query = query.Where("name ILIKE ?", "%"+req.Search+"%")
|
|
}
|
|
|
|
if req.Name != "" {
|
|
query = query.Where("name ILIKE ?", "%"+req.Name+"%")
|
|
}
|
|
|
|
if req.PartnerID != nil {
|
|
query = query.Where("partner_id = ?", req.PartnerID)
|
|
}
|
|
|
|
if req.SiteID != nil {
|
|
query = query.Where("id = ?", req.SiteID)
|
|
}
|
|
|
|
if req.Limit > 0 {
|
|
query = query.Limit(req.Limit)
|
|
}
|
|
|
|
if req.Offset > 0 {
|
|
query = query.Offset(req.Offset)
|
|
}
|
|
|
|
if err := query.Find(&sites).Error; err != nil {
|
|
logger.ContextLogger(ctx).Error("error when getting all sites", zap.Error(err))
|
|
return nil, 0, err
|
|
}
|
|
|
|
if err := r.db.Model(&entity.SiteDB{}).Where(query).Count(&total).Error; err != nil {
|
|
logger.ContextLogger(ctx).Error("error when counting sites", zap.Error(err))
|
|
return nil, 0, err
|
|
}
|
|
|
|
return sites, int(total), nil
|
|
}
|
|
|
|
func (r *SiteRepository) Delete(ctx context.Context, id int64) error {
|
|
site := new(entity.SiteDB)
|
|
site.ID = id
|
|
if err := r.db.Delete(site).Error; err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *SiteRepository) Count(ctx mycontext.Context, req entity.SiteSearch) (*entity.SiteCountDB, error) {
|
|
count := new(entity.SiteCountDB)
|
|
|
|
query := r.db.Table("sites").
|
|
Select("count(*) as count")
|
|
|
|
if !req.IsAdmin {
|
|
query = query.Where("partner_id = ?", req.PartnerID)
|
|
}
|
|
|
|
if err := query.Scan(&count).Error; err != nil {
|
|
logger.ContextLogger(ctx).Error("error when get count sites", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
return count, nil
|
|
}
|
|
|
|
func (r *SiteRepository) GetNearestSites(ctx context.Context, latitude, longitude, radius float64) ([]entity.SiteProductInfo, error) {
|
|
const limit = 5
|
|
var siteProducts []entity.SiteProductInfo
|
|
|
|
distanceQuery := `
|
|
(6371 * acos(cos(radians(?)) * cos(radians(latitude)) * cos(radians(longitude) - radians(?)) + sin(radians(?)) * sin(radians(latitude))))
|
|
`
|
|
|
|
// Primary query for sites within the radius
|
|
err := r.db.WithContext(ctx).Raw(`
|
|
SELECT s.id AS site_id, s.name AS site_name, s.region, s.regency, s.partner_id, s.image, s.address, s.location_link, s.description,
|
|
s.highlight, s.contact_person, s.tnc, s.additional_info, s.status, s.is_season_ticket, s.is_discount_active,
|
|
s.latitude, s.longitude, s.created_at, s.updated_at,
|
|
`+distanceQuery+` AS distance,
|
|
p.id AS product_id, p.name AS product_name, p.type AS product_type, p.price AS product_price,
|
|
p.is_weekend_ticket, p.is_season_ticket, p.status AS product_status, p.description AS product_description
|
|
FROM sites s
|
|
LEFT JOIN (
|
|
SELECT *, ROW_NUMBER() OVER (PARTITION BY site_id ORDER BY price ASC) AS rn
|
|
FROM products
|
|
) p ON s.id = p.site_id AND p.rn = 1
|
|
WHERE `+distanceQuery+` < ?
|
|
ORDER BY distance
|
|
LIMIT ?`,
|
|
latitude, longitude, latitude, latitude, longitude, latitude, radius, limit).Scan(&siteProducts).Error
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If fewer than 5 sites found, fetch additional ones regardless of distance
|
|
if len(siteProducts) < limit {
|
|
additionalLimit := limit - len(siteProducts)
|
|
err = r.db.WithContext(ctx).Raw(`
|
|
SELECT s.id AS site_id, s.name AS site_name, s.region, s.regency, s.partner_id, s.image, s.address, s.location_link, s.description,
|
|
s.highlight, s.contact_person, s.tnc, s.additional_info, s.status, s.is_season_ticket, s.is_discount_active,
|
|
s.latitude, s.longitude, s.created_at, s.updated_at,
|
|
`+distanceQuery+` AS distance,
|
|
p.id AS product_id, p.name AS product_name, p.type AS product_type, p.price AS product_price,
|
|
p.is_weekend_ticket, p.is_season_ticket, p.status AS product_status, p.description AS product_description
|
|
FROM sites s
|
|
LEFT JOIN (
|
|
SELECT *, ROW_NUMBER() OVER (PARTITION BY site_id ORDER BY price ASC) AS rn
|
|
FROM products
|
|
) p ON s.id = p.site_id AND p.rn = 1
|
|
ORDER BY distance
|
|
LIMIT ?`,
|
|
latitude, longitude, latitude, additionalLimit).Scan(&siteProducts).Error
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return siteProducts, nil
|
|
}
|
|
|
|
func (r *SiteRepository) SearchSites(ctx context.Context, search *entity.DiscoverySearch) ([]entity.SiteProductInfo, int64, error) {
|
|
var siteProducts []entity.SiteProductInfo
|
|
var total int64
|
|
|
|
// Adding wildcard for partial matching
|
|
searchName := "%" + search.Name + "%"
|
|
|
|
// Base conditions and parameters
|
|
conditions := "s.name ILIKE ?"
|
|
params := []interface{}{searchName}
|
|
|
|
// Add region filtering if region is provided
|
|
if search.Region != "" {
|
|
conditions += " AND s.region = ?"
|
|
params = append(params, search.Region)
|
|
}
|
|
|
|
if search.Status != "" {
|
|
conditions += " AND s.status = ?"
|
|
params = append(params, search.Status)
|
|
}
|
|
|
|
if search.Discover != "" {
|
|
conditions += " AND s.address ILIKE ?"
|
|
params = append(params, "%"+search.Discover+"%")
|
|
}
|
|
|
|
// Count query to get the total number of matching records
|
|
countQuery := `
|
|
SELECT COUNT(*)
|
|
FROM sites s
|
|
WHERE ` + conditions
|
|
err := r.db.WithContext(ctx).Raw(countQuery, params...).Scan(&total).Error
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
// Add limit and offset for the data query
|
|
dataParams := append(params, search.Limit, search.Offset)
|
|
|
|
// Primary query for sites matching name and region, with pagination
|
|
dataQuery := `
|
|
SELECT s.id AS site_id, s.name AS site_name, s.region, s.regency, s.partner_id, s.image, s.address, s.location_link, s.description,
|
|
s.highlight, s.contact_person, s.tnc, s.additional_info, s.status, s.is_season_ticket, s.is_discount_active,
|
|
s.latitude, s.longitude, s.created_at, s.updated_at,
|
|
p.id AS product_id, p.name AS product_name, p.type AS product_type, p.price AS product_price,
|
|
p.is_weekend_ticket, p.is_season_ticket, p.status AS product_status, p.description AS product_description
|
|
FROM sites s
|
|
LEFT JOIN (
|
|
SELECT *, ROW_NUMBER() OVER (PARTITION BY site_id ORDER BY price ASC) AS rn
|
|
FROM products
|
|
) p ON s.id = p.site_id AND p.rn = 1
|
|
WHERE ` + conditions + `
|
|
ORDER BY s.name
|
|
LIMIT ? OFFSET ?
|
|
`
|
|
err = r.db.WithContext(ctx).Raw(dataQuery, dataParams...).Scan(&siteProducts).Error
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return siteProducts, total, nil
|
|
}
|