github.com/litesolutions/justifay-api@v1.0.0-2.0.20220707114139-46f28a909481/server/usergroup.go (about) 1 package server 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "net/url" 8 "regexp" 9 "time" 10 11 uuid "github.com/google/uuid" 12 "github.com/goware/urlx" 13 "github.com/uptrace/bun" 14 15 "github.com/litesolutions/justifay-api/model" 16 pbUser "github.com/litesolutions/justifay-api/proto/user" 17 ) 18 19 // // Server implements the UserService 20 // type Server struct { 21 // db *bun.DB 22 // } 23 24 // // New creates an instance of our server 25 // func New(db *bun.DB) *Server { 26 // return &Server{db: db} 27 // } 28 29 // AddUser gets a user to the in-memory store. 30 func (s *Server) AddUserGroup(ctx context.Context, usergroup *pbUser.UserGroupCreateRequest) (*pbUser.UserRequest, error) { 31 32 requiredErr := s.checkRequiredAddUserGroupAttributes(ctx, usergroup) 33 34 if requiredErr != nil { 35 return nil, requiredErr 36 } 37 38 OwnerUUID, err := uuid.Parse(usergroup.Id) 39 40 if err != nil { 41 return nil, errors.New("supplied user_id is not a valid UUID") 42 } 43 44 existingGroupCount, _ := s.db.NewSelect(). 45 Model((*model.UserGroup)(nil)). 46 Where("owner_id = ?", OwnerUUID). 47 Count(ctx) 48 49 owningUser := new(model.User) 50 51 err = s.db.NewSelect(). 52 Model(owningUser). 53 Where("id = ?", OwnerUUID). 54 Scan(ctx) 55 56 if err != nil { 57 return nil, errors.New("supplied owner_id could not be found in Users") 58 } 59 60 if owningUser.RoleID == int32(model.UserRole) && existingGroupCount > 0 { 61 return nil, errors.New("supplied owner_id is a user and already has a user group profile") 62 } 63 64 group := new(model.GroupType) 65 66 err = s.db.NewSelect(). 67 Model(group). 68 Where("name = ?", usergroup.GroupType). 69 Scan(ctx) 70 71 if err != nil { 72 return nil, errors.New("supplied group type is not valid") 73 } 74 75 AvatarUUID, err := uuid.Parse(usergroup.Avatar) 76 77 if usergroup.Avatar != "" && err != nil { 78 return nil, errors.New("supplied avatar is not a valid UUID") 79 } 80 81 BannerUUID, err := uuid.Parse(usergroup.Banner) 82 83 if usergroup.Banner != "" && err != nil { 84 return nil, errors.New("supplied banner is not a valid UUID") 85 } 86 87 newUserGroup := &model.UserGroup{ 88 OwnerID: OwnerUUID, 89 TypeID: group.ID, 90 Type: group, 91 DisplayName: usergroup.DisplayName, 92 Description: usergroup.Description, 93 ShortBio: usergroup.ShortBio, 94 Avatar: AvatarUUID, 95 Banner: BannerUUID, 96 GroupEmail: usergroup.GroupEmail, 97 } 98 99 if usergroup.Tags != nil { 100 tags := make([]model.Tag, len(usergroup.Tags)) 101 names := make([]string, len(usergroup.Tags)) 102 tagType := "genre" // defaults to genre tags for now 103 104 for i := range usergroup.Tags { 105 tag := model.Tag{ 106 Name: usergroup.Tags[i], 107 Type: tagType, 108 } 109 tag.ID = uuid.Must(uuid.NewRandom()) 110 names[i] = tag.Name 111 tags[i] = tag 112 } 113 114 existing := []model.Tag{} 115 116 // find existing tags 117 err := s.db.NewSelect(). 118 Model(&existing). 119 Where("type = ? AND name IN (?)", tagType, bun.In(names)). 120 Scan(ctx) 121 122 if err != nil { 123 return nil, err 124 } 125 126 var result []uuid.UUID 127 var insert []model.Tag 128 129 for l := range tags { 130 var seen uuid.UUID 131 132 for e := range existing { 133 if existing[e].Name == tags[l].Name { 134 seen = existing[e].ID 135 break 136 } 137 } 138 139 if seen == uuid.Nil { 140 insert = append(insert, tags[l]) 141 result = append(result, tags[l].ID) 142 } else { 143 result = append(result, seen) 144 } 145 } 146 147 if len(insert) > 0 { 148 _, err := s.db. 149 NewInsert(). 150 Model(&insert). 151 Exec(ctx) 152 153 if err != nil { 154 return nil, err 155 } 156 } 157 158 newUserGroup.Tags = result 159 } 160 161 if usergroup.Links != nil { 162 uris := make([]string, len(usergroup.Links)) 163 links := make([]model.Link, len(usergroup.Links)) 164 165 for i := range usergroup.Links { 166 uri := usergroup.Links[i] 167 168 platform := s.getPlatform(uri) 169 170 link := model.Link{ 171 URI: usergroup.Links[i], 172 Platform: platform, 173 } 174 link.ID = uuid.Must(uuid.NewRandom()) 175 uris[i] = link.URI 176 links[i] = link 177 } 178 179 existing := []model.Link{} 180 181 // find existing links 182 err = s.db.NewSelect(). 183 Model(&existing). 184 Where("uri IN (?)", bun.In(uris)). 185 Scan(ctx) 186 187 if err != nil { 188 return nil, err 189 } 190 191 var result []uuid.UUID 192 var insert []model.Link 193 194 for l := range links { 195 var seen uuid.UUID 196 197 for e := range existing { 198 if existing[e].URI == links[l].URI { 199 seen = existing[e].ID 200 break 201 } 202 } 203 204 if seen == uuid.Nil { 205 insert = append(insert, links[l]) 206 result = append(result, links[l].ID) 207 } else { 208 result = append(result, seen) 209 } 210 } 211 212 if len(insert) > 0 { 213 _, err := s.db. 214 NewInsert(). 215 Model(&insert). 216 Exec(ctx) 217 218 if err != nil { 219 return nil, err 220 } 221 } 222 223 newUserGroup.Links = result 224 } 225 226 newUserGroup.ID = uuid.Must(uuid.NewRandom()) 227 newUserGroup.CreatedAt = time.Now().UTC() 228 229 _, err = s.db.NewInsert().Model(newUserGroup).Exec(ctx) 230 231 if err != nil { 232 return nil, err 233 } 234 235 return &pbUser.UserRequest{Id: newUserGroup.ID.String()}, nil 236 } 237 238 // UpdateUser updates a users basic attributes 239 func (s *Server) UpdateUserGroup(ctx context.Context, UserGroupUpdateRequest *pbUser.UserGroupUpdateRequest) (*pbUser.Empty, error) { 240 241 var updatedUserGroupValues = make(map[string]interface{}) 242 243 if UserGroupUpdateRequest.GroupEmail != nil { 244 updatedUserGroupValues["group_email_address"] = *UserGroupUpdateRequest.GroupEmail 245 re := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") 246 if !re.MatchString(*UserGroupUpdateRequest.GroupEmail) { 247 return nil, errors.New("group email address must be a valid email") 248 } 249 } 250 if UserGroupUpdateRequest.DisplayName != nil { 251 updatedUserGroupValues["display_name"] = *UserGroupUpdateRequest.DisplayName 252 } 253 if UserGroupUpdateRequest.Description != nil { 254 updatedUserGroupValues["description"] = *UserGroupUpdateRequest.Description 255 } 256 if UserGroupUpdateRequest.ShortBio != nil { 257 updatedUserGroupValues["short_bio"] = *UserGroupUpdateRequest.ShortBio 258 } 259 if UserGroupUpdateRequest.GroupType != nil { 260 group := new(model.GroupType) 261 262 err := s.db.NewSelect(). 263 Model(group). 264 Where("name = ?", UserGroupUpdateRequest.GroupType). 265 Scan(ctx) 266 267 if err != nil { 268 return nil, errors.New("supplied group type is not valid") 269 } 270 271 updatedUserGroupValues["type_id"] = group.ID 272 } 273 if UserGroupUpdateRequest.OwnerId != nil { 274 updatedUserGroupValues["owner_id"] = *UserGroupUpdateRequest.OwnerId 275 } 276 if UserGroupUpdateRequest.Avatar != nil { 277 updatedUserGroupValues["avatar"] = *UserGroupUpdateRequest.Avatar 278 } 279 if UserGroupUpdateRequest.Banner != nil { 280 updatedUserGroupValues["banner"] = *UserGroupUpdateRequest.Banner 281 } 282 283 if UserGroupUpdateRequest.Tags != nil { 284 tags := make([]model.Tag, len(UserGroupUpdateRequest.Tags)) 285 names := make([]string, len(UserGroupUpdateRequest.Tags)) 286 tagType := "genre" // defaults to genre tags for now 287 288 for i := range UserGroupUpdateRequest.Tags { 289 tag := model.Tag{ 290 Name: UserGroupUpdateRequest.Tags[i], 291 Type: tagType, 292 } 293 tag.ID = uuid.Must(uuid.NewRandom()) 294 names[i] = tag.Name 295 tags[i] = tag 296 } 297 298 existing := []model.Tag{} 299 300 // find existing tags 301 err := s.db.NewSelect(). 302 Model(&existing). 303 Where("type = ? AND name IN (?)", tagType, bun.In(names)). 304 Scan(ctx) 305 306 if err != nil { 307 return nil, err 308 } 309 310 var result []uuid.UUID 311 var insert []model.Tag 312 313 for l := range tags { 314 var seen uuid.UUID 315 316 for e := range existing { 317 if existing[e].Name == tags[l].Name { 318 seen = existing[e].ID 319 break 320 } 321 } 322 323 if seen == uuid.Nil { 324 insert = append(insert, tags[l]) 325 result = append(result, tags[l].ID) 326 } else { 327 result = append(result, seen) 328 } 329 } 330 331 if len(insert) > 0 { 332 _, err := s.db. 333 NewInsert(). 334 Model(&insert). 335 Exec(ctx) 336 337 if err != nil { 338 return nil, err 339 } 340 } 341 342 updatedUserGroupValues["tags"] = result 343 } 344 345 if UserGroupUpdateRequest.Links != nil { 346 links := make([]model.Link, len(UserGroupUpdateRequest.Links)) 347 uris := make([]string, len(UserGroupUpdateRequest.Links)) 348 349 for i := range UserGroupUpdateRequest.Links { 350 uri := UserGroupUpdateRequest.Links[i] 351 platform := s.getPlatform(uri) 352 353 link := model.Link{ 354 URI: uri, 355 Platform: platform, 356 } 357 link.ID = uuid.Must(uuid.NewRandom()) 358 uris[i] = link.URI 359 links[i] = link 360 } 361 362 existing := []model.Link{} 363 364 // find existing links 365 err := s.db.NewSelect(). 366 Model(&existing). 367 Where("uri IN (?)", bun.In(uris)). 368 Scan(ctx) 369 370 if err != nil { 371 return nil, err 372 } 373 374 var result []uuid.UUID 375 var insert []model.Link 376 377 for l := range links { 378 var seen uuid.UUID 379 380 for e := range existing { 381 if existing[e].URI == links[l].URI { 382 seen = existing[e].ID 383 break 384 } 385 } 386 387 if seen == uuid.Nil { 388 insert = append(insert, links[l]) 389 result = append(result, links[l].ID) 390 } else { 391 result = append(result, seen) 392 } 393 } 394 395 if len(insert) > 0 { 396 _, err := s.db. 397 NewInsert(). 398 Model(&insert). 399 Exec(ctx) 400 401 if err != nil { 402 return nil, err 403 } 404 } 405 406 // TODO prune orphan links? 407 408 updatedUserGroupValues["links"] = result 409 } 410 411 updatedUserGroupValues["updated_at"] = time.Now().UTC() 412 413 rows, err := s.db.NewUpdate().Model(&updatedUserGroupValues).TableExpr("user_groups").Where("id = ?", UserGroupUpdateRequest.Id).Exec(ctx) 414 415 if err != nil { 416 return nil, err 417 } 418 419 number, _ := rows.RowsAffected() 420 421 if number == 0 { 422 return nil, errors.New("warning: no rows were updated") 423 } 424 425 return &pbUser.Empty{}, nil 426 } 427 428 // DeleteUser Deletes a user from the DB 429 func (s *Server) DeleteUserGroup(ctx context.Context, usergroup *pbUser.UserGroupRequest) (*pbUser.Empty, error) { 430 u := new(model.UserGroup) 431 432 _, err := s.db.NewDelete(). 433 Model(u). 434 Where("id = ?", usergroup.Id). 435 Exec(ctx) 436 437 if err != nil { 438 return nil, err 439 } 440 441 return &pbUser.Empty{}, nil 442 } 443 444 // GetUserGroup returns details of single user group 445 func (s *Server) GetUserGroup(ctx context.Context, usergrouprequest *pbUser.UserGroupRequest) (*pbUser.UserGroupPublicResponse, error) { 446 447 usergroup := new(model.UserGroup) 448 449 err := s.db.NewSelect(). 450 Model(usergroup). 451 Where("id = ?", usergrouprequest.Id). 452 Scan(ctx) 453 454 if err != nil { 455 return nil, err 456 } 457 458 group := new(model.GroupType) 459 460 err = s.db.NewSelect(). 461 Model(group). 462 Where("id = ?", usergroup.TypeID). 463 Scan(ctx) 464 465 if err != nil { 466 return nil, errors.New("supplied group type is not valid") 467 } 468 469 links := []model.Link{} 470 471 if len(usergroup.Links) > 0 { 472 err = s.db.NewSelect(). 473 Model(&links). 474 Where("id IN (?)", bun.In(usergroup.Links)). 475 Scan(ctx) 476 477 if err != nil { 478 return nil, err 479 } 480 } 481 482 usergroupLinks := []*pbUser.Link{} 483 484 if len(links) > 0 { 485 for i := range links { 486 usergroupLinks = append(usergroupLinks, &pbUser.Link{ 487 Platform: links[i].Platform, 488 Uri: links[i].URI, 489 }) 490 } 491 } 492 493 return &pbUser.UserGroupPublicResponse{ 494 DisplayName: usergroup.DisplayName, 495 GroupType: group.Name, 496 ShortBio: usergroup.ShortBio, 497 Description: usergroup.Description, 498 Links: usergroupLinks, 499 Avatar: uuid.UUID.String(usergroup.Avatar), 500 Banner: uuid.UUID.String(usergroup.Banner), 501 GroupEmail: usergroup.GroupEmail, 502 }, nil 503 } 504 505 // ListUsersUserGroups lists all the User Groups owned by the supplied User Id 506 func (s *Server) ListUsersUserGroups(ctx context.Context, user *pbUser.UserRequest) (*pbUser.UserGroupListResponse, error) { 507 508 var usergroups []model.UserGroup 509 var results pbUser.UserGroupListResponse 510 511 err := s.db.NewSelect(). 512 Model(&usergroups). 513 Where("owner_id = ?", user.Id). 514 Order("created_at ASC"). 515 Scan(ctx) 516 517 if err != nil { 518 return nil, err 519 } 520 521 for _, usergroup := range usergroups { 522 523 group := new(model.GroupType) 524 525 err = s.db.NewSelect(). 526 Model(group). 527 Where("id = ?", usergroup.TypeID). 528 Scan(ctx) 529 530 var result pbUser.UserGroupPrivateResponse 531 result.Id = uuid.UUID.String(usergroup.ID) 532 result.DisplayName = usergroup.DisplayName 533 result.GroupType = group.Name 534 result.ShortBio = usergroup.ShortBio 535 result.Description = usergroup.Description 536 result.Avatar = uuid.UUID.String(usergroup.Avatar) 537 result.Banner = uuid.UUID.String(usergroup.Banner) 538 result.GroupEmail = usergroup.GroupEmail 539 result.CreatedAt = usergroup.CreatedAt.UTC().String() 540 result.UpdatedAt = usergroup.UpdatedAt.UTC().String() 541 542 results.Usergroup = append(results.Usergroup, &result) 543 } 544 545 return &results, nil 546 } 547 548 func (s *Server) checkRequiredAddUserGroupAttributes(ctx context.Context, usergroup *pbUser.UserGroupCreateRequest) error { 549 if usergroup.Id == "" || usergroup.Id == uuid.Nil.String() || usergroup.DisplayName == "" { 550 var argument string 551 switch { 552 case usergroup.Id == "": 553 argument = "owner_id" 554 case usergroup.Id == uuid.Nil.String(): 555 argument = "owner_id" 556 case usergroup.DisplayName == "": 557 argument = "display_name" 558 } 559 return fmt.Errorf("argument %v is required", argument) 560 } 561 562 if usergroup.GroupEmail != "" { 563 re := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$") 564 if !re.MatchString(usergroup.GroupEmail) { 565 return errors.New("group email address must be a valid email") 566 } 567 } 568 569 if usergroup.Links != nil { 570 for i := range usergroup.Links { 571 uri := usergroup.Links[i] 572 573 _, err := url.ParseRequestURI(uri) 574 575 if err != nil { 576 return fmt.Errorf("invalid url %v", uri) 577 } 578 } 579 } 580 581 err := s.db.NewSelect(). 582 Model(new(model.User)). 583 Where("id = ?", usergroup.Id). 584 Scan(ctx) 585 586 if err != nil { 587 return errors.New("supplied owner_id does not exist") 588 } 589 590 err = s.db.NewSelect(). 591 Model(new(model.GroupType)). 592 Where("name = ?", usergroup.GroupType). 593 Scan(ctx) 594 595 if err != nil { 596 return errors.New("supplied group type does not exist") 597 } 598 599 return nil 600 } 601 602 func (s *Server) getPlatform(uri string) string { 603 parsed, _ := urlx.Parse(uri) 604 hostname := parsed.Hostname() 605 platform := "" 606 607 switch { 608 case hostname == "youtube.com" || hostname == "youtu.be": 609 platform = "youtube" 610 case hostname == "facebook.com": 611 platform = "facebook" 612 case hostname == "soundcloud.com": 613 platform = "soundcloud" 614 case hostname == "twitter.com": 615 platform = "twitter" 616 case hostname == "bandcamp.com": 617 platform = "bandcamp" 618 case hostname == "vimeo.com": 619 platform = "vimeo" 620 } 621 622 return platform 623 }