github.com/e154/smart-home@v0.17.2-0.20240311175135-e530a6e5cd45/db/area.go (about)

     1  // This file is part of the Smart Home
     2  // Program complex distribution https://github.com/e154/smart-home
     3  // Copyright (C) 2016-2023, Filippov Alex
     4  //
     5  // This library is free software: you can redistribute it and/or
     6  // modify it under the terms of the GNU Lesser General Public
     7  // License as published by the Free Software Foundation; either
     8  // version 3 of the License, or (at your option) any later version.
     9  //
    10  // This library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    13  // Library General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public
    16  // License along with this library.  If not, see
    17  // <https://www.gnu.org/licenses/>.
    18  
    19  package db
    20  
    21  import (
    22  	"context"
    23  	"encoding/json"
    24  	"fmt"
    25  	"strings"
    26  	"time"
    27  
    28  	"github.com/jackc/pgx/v5/pgconn"
    29  
    30  	"github.com/jackc/pgerrcode"
    31  	"github.com/pkg/errors"
    32  	"gorm.io/gorm"
    33  
    34  	"github.com/e154/smart-home/common/apperr"
    35  )
    36  
    37  // Areas ...
    38  type Areas struct {
    39  	Db *gorm.DB
    40  }
    41  
    42  // Area ...
    43  type Area struct {
    44  	Id          int64 `gorm:"primary_key"`
    45  	Name        string
    46  	Description string
    47  	Polygon     *Polygon
    48  	Payload     json.RawMessage `gorm:"type:jsonb;not null"`
    49  	CreatedAt   time.Time       `gorm:"<-:create"`
    50  	UpdatedAt   time.Time
    51  }
    52  
    53  // TableName ...
    54  func (d *Area) TableName() string {
    55  	return "areas"
    56  }
    57  
    58  // Add ...
    59  func (n *Areas) Add(ctx context.Context, area *Area) (id int64, err error) {
    60  	if err = n.Db.WithContext(ctx).Create(&area).Error; err != nil {
    61  		var pgErr *pgconn.PgError
    62  		if errors.As(err, &pgErr) {
    63  			switch pgErr.Code {
    64  			case pgerrcode.UniqueViolation:
    65  				if strings.Contains(pgErr.Message, "name_at_areas_unq") {
    66  					err = errors.Wrap(apperr.ErrAreaAdd, fmt.Sprintf("area name \"%s\" not unique", area.Name))
    67  					return
    68  				}
    69  			default:
    70  				fmt.Printf("unknown code \"%s\"\n", pgErr.Code)
    71  			}
    72  		}
    73  		err = errors.Wrap(apperr.ErrAreaAdd, err.Error())
    74  		return
    75  	}
    76  	id = area.Id
    77  	return
    78  }
    79  
    80  // GetByName ...
    81  func (n *Areas) GetByName(ctx context.Context, name string) (area *Area, err error) {
    82  
    83  	area = &Area{}
    84  	err = n.Db.WithContext(ctx).Model(area).
    85  		Where("name = ?", name).
    86  		First(&area).
    87  		Error
    88  
    89  	if err != nil {
    90  		err = errors.Wrap(apperr.ErrAreaGet, err.Error())
    91  	}
    92  	return
    93  }
    94  
    95  // Search ...
    96  func (n *Areas) Search(ctx context.Context, query string, limit, offset int) (list []*Area, total int64, err error) {
    97  
    98  	q := n.Db.WithContext(ctx).Model(&Area{}).
    99  		Where("name LIKE ?", "%"+query+"%")
   100  
   101  	if err = q.Count(&total).Error; err != nil {
   102  		err = errors.Wrap(apperr.ErrAreaGet, err.Error())
   103  		return
   104  	}
   105  
   106  	q = q.
   107  		Limit(limit).
   108  		Offset(offset).
   109  		Order("name ASC")
   110  
   111  	list = make([]*Area, 0)
   112  	if err = q.Find(&list).Error; err != nil {
   113  		err = errors.Wrap(apperr.ErrAreaGet, err.Error())
   114  	}
   115  
   116  	return
   117  }
   118  
   119  // DeleteByName ...
   120  func (n *Areas) DeleteByName(ctx context.Context, name string) (err error) {
   121  	if name == "" {
   122  		err = errors.Wrap(apperr.ErrAreaDelete, "zero name")
   123  		return
   124  	}
   125  
   126  	if err = n.Db.WithContext(ctx).Delete(&Area{}, "name = ?", name).Error; err != nil {
   127  		err = errors.Wrap(apperr.ErrAreaDelete, "zero name")
   128  	}
   129  	return
   130  }
   131  
   132  // Clean ...
   133  func (n *Areas) Clean(ctx context.Context) (err error) {
   134  
   135  	err = n.Db.WithContext(ctx).Exec(`delete 
   136  from areas
   137  where id not in (
   138      select DISTINCT me.area_id
   139      from entities me
   140      where me.area_id notnull
   141      )
   142  `).Error
   143  
   144  	if err != nil {
   145  		err = errors.Wrap(apperr.ErrAreaClean, "zero name")
   146  	}
   147  
   148  	return
   149  }
   150  
   151  // Update ...
   152  func (n *Areas) Update(ctx context.Context, area *Area) (err error) {
   153  	err = n.Db.WithContext(ctx).Model(&Area{Id: area.Id}).Updates(map[string]interface{}{
   154  		"name":        area.Name,
   155  		"description": area.Description,
   156  		"payload":     area.Payload,
   157  		"polygon":     area.Polygon,
   158  	}).Error
   159  
   160  	if err != nil {
   161  		var pgErr *pgconn.PgError
   162  		if errors.As(err, &pgErr) {
   163  			switch pgErr.Code {
   164  			case pgerrcode.UniqueViolation:
   165  				if strings.Contains(pgErr.Message, "name_at_areas_unq") {
   166  					err = errors.Wrap(apperr.ErrAreaUpdate, fmt.Sprintf("area name \"%s\" not unique", area.Name))
   167  					return
   168  				}
   169  			default:
   170  				fmt.Printf("unknown code \"%s\"\n", pgErr.Code)
   171  			}
   172  		}
   173  		err = errors.Wrap(apperr.ErrAreaUpdate, err.Error())
   174  	}
   175  	return
   176  }
   177  
   178  // List ...
   179  func (n *Areas) List(ctx context.Context, limit, offset int, orderBy, sort string) (list []*Area, total int64, err error) {
   180  
   181  	if err = n.Db.WithContext(ctx).Model(Area{}).Count(&total).Error; err != nil {
   182  		err = errors.Wrap(apperr.ErrAreaList, err.Error())
   183  		return
   184  	}
   185  
   186  	list = make([]*Area, 0)
   187  	q := n.Db.WithContext(ctx).Model(&Area{}).
   188  		Limit(limit).
   189  		Offset(offset)
   190  
   191  	if sort != "" && orderBy != "" {
   192  		q = q.
   193  			Order(fmt.Sprintf("%s %s", sort, orderBy))
   194  	}
   195  
   196  	err = q.
   197  		Find(&list).
   198  		Error
   199  	if err != nil {
   200  		err = errors.Wrap(apperr.ErrAreaList, err.Error())
   201  	}
   202  
   203  	return
   204  }
   205  
   206  // GetById ...
   207  func (n *Areas) GetById(ctx context.Context, areaId int64) (area *Area, err error) {
   208  	area = &Area{Id: areaId}
   209  	if err = n.Db.WithContext(ctx).First(&area).Error; err != nil {
   210  		if errors.Is(err, gorm.ErrRecordNotFound) {
   211  			err = errors.Wrap(apperr.ErrAreaNotFound, fmt.Sprintf("id \"%d\"", areaId))
   212  			return
   213  		}
   214  		err = errors.Wrap(apperr.ErrAreaGet, err.Error())
   215  	}
   216  	return
   217  }
   218  
   219  //func (a *Areas) ListByPoint(ctx context.Context, point Point, limit, offset int) (list []*Area, err error) {
   220  //
   221  //	// https://postgis.net/docs/ST_Point.html
   222  //	// geometry ST_Point(float x, float y);
   223  //	// For geodetic coordinates, X is longitude and Y is latitude
   224  //
   225  //	const query = `
   226  //SELECT *
   227  //		FROM areas as a
   228  //WHERE ST_Contains(a.polygon::geometry,
   229  //		ST_Transform(
   230  //			ST_GeomFromText('POINT(%f %f)', 4326), 4326
   231  //		)
   232  //	)`
   233  //
   234  //	list = make([]*Area, 0)
   235  //	q := fmt.Sprintf(query, point.Lon, point.Lat)
   236  //
   237  //	err = a.Db.WithContext(ctx).Raw(q).
   238  //		Limit(limit).
   239  //		Offset(offset).Scan(&list).Error
   240  //	if err != nil {
   241  //		err = errors.Wrap(apperr.ErrAreaList, err.Error())
   242  //		return
   243  //	}
   244  //
   245  //	return
   246  //}
   247  
   248  //func (a *Areas) PointInsideAriaById(ctx context.Context, point Point, areaId int64) (inside bool, err error) {
   249  //
   250  //	var list []*Area
   251  //	if list, err = a.ListByPoint(ctx, point, 999, 0); err != nil {
   252  //		return
   253  //	}
   254  //
   255  //	for _, area := range list {
   256  //		if inside = areaId == area.Id; inside {
   257  //			return
   258  //		}
   259  //	}
   260  //
   261  //	return
   262  //}
   263  //
   264  //func (a *Areas) PointInsideAriaByName(ctx context.Context, point Point, areaName string) (inside bool, err error) {
   265  //
   266  //	var list []*Area
   267  //	if list, err = a.ListByPoint(ctx, point, 999, 0); err != nil {
   268  //		return
   269  //	}
   270  //
   271  //	for _, area := range list {
   272  //		if inside = areaName == area.Name; inside {
   273  //			return
   274  //		}
   275  //	}
   276  //
   277  //	return
   278  //}
   279  //
   280  //func (a *Areas) GetDistanceToArea(ctx context.Context, point Point, areaId int64) (distance float64, err error) {
   281  //
   282  //	const query = `
   283  //select st_distance(
   284  //   ST_Transform(ST_GeomFromText('POINT (%f %f)', 4326)::geometry, 4326),
   285  //   ST_Transform((select polygon from areas where id = %d)::geometry, 4326)
   286  //)`
   287  //	q := fmt.Sprintf(query, point.Lat, point.Lon, areaId)
   288  //	err = a.Db.WithContext(ctx).Raw(q).Scan(&distance).Error
   289  //	if err != nil {
   290  //		err = errors.Wrap(apperr.ErrAreaList, err.Error())
   291  //		return
   292  //	}
   293  //
   294  //	return
   295  //}
   296  //
   297  //func (a *Areas) GetDistanceBetweenPoints(ctx context.Context, point1, point2 Point) (distance float64, err error) {
   298  //
   299  //	const query = `
   300  //select st_distance(
   301  //   ST_Transform(ST_GeomFromText('POINT (%f %f)', 4326)::geometry, 4326),
   302  //   ST_Transform(ST_GeomFromText('POINT (%f %f)', 4326)::geometry, 4326)
   303  //)`
   304  //	q := fmt.Sprintf(query, point1.Lat, point1.Lon, point2.Lat, point2.Lon)
   305  //	err = a.Db.WithContext(ctx).Raw(q).Scan(&distance).Error
   306  //	if err != nil {
   307  //		err = errors.Wrap(apperr.ErrAreaList, err.Error())
   308  //		return
   309  //	}
   310  //
   311  //	return
   312  //}