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 //}