github.com/resonatecoop/user-api@v1.0.0-13.0.20220915120639-05dc9c04014a/model/user_group.go (about) 1 package model 2 3 import ( 4 5 // "log" 6 7 "github.com/go-pg/pg" 8 "github.com/go-pg/pg/orm" 9 10 // trackpb "user-api/rpc/track" 11 //tagpb "github.com/resonatecoop/user-api/proto/api" 12 13 pbUser "github.com/resonatecoop/user-api/proto/user" 14 15 uuid "github.com/google/uuid" 16 ) 17 18 // UserGroup represents a group of Users and maintains a set of metadata 19 type UserGroup struct { 20 IDRecord 21 DisplayName string `bun:",unique,notnull"` 22 Description string 23 ShortBio string 24 GroupEmail string 25 AddressID uuid.UUID `bun:"type:uuid,notnull"` //for Country see User model 26 Address *StreetAddress 27 TypeID uuid.UUID `bun:"type:uuid,notnull"` //for e.g. Persona Type 28 Type *GroupType 29 OwnerID uuid.UUID `bun:"type:uuid,notnull"` 30 Owner *User `bun:"rel:has-one"` 31 Links []uuid.UUID `bun:",type:uuid[],array"` 32 Members []UserGroup `pg:"many2many:user_group_members,fk:user_group_id,joinFK:member_id"` 33 MemberOfGroups []UserGroup `pg:"many2many:user_group_members,fk:member_id,joinFK:user_group_id"` 34 Avatar uuid.UUID `bun:"type:uuid"` 35 Banner uuid.UUID `bun:"type:uuid"` 36 Tags []uuid.UUID `bun:",type:uuid[],array"` 37 // AdminUsers []uuid.UUID `bun:",type:uuid[]" pg:",array"` 38 // Followers []uuid.UUID `bun:",type:uuid[]" pg:",array"` 39 // RecommendedArtists []uuid.UUID `bun:",type:uuid[]" pg:",array"` 40 // RecommendedBy []uuid.UUID `bun:",type:uuid[]" pg:",array"` 41 // PrivacyID uuid.UUID `bun:"type:uuid,notnull"` 42 // Privacy *UserGroupPrivacy 43 // Kvstore map[string]string `pg:",hstore"` 44 // Publisher map[string]string `pg:",hstore"` 45 // Pro map[string]string `pg:",hstore"` 46 } 47 48 // Address *StreetAddress 49 // Type GroupType 50 51 //TypeID uuid.UUID `bun:"type:uuid,notnull"` 52 //AddressID uuid.UUID `bun:"type:uuid,notnull"` 53 //HighlightedTracks []uuid.UUID `bun:",type:uuid[]" pg:",array"` 54 //FeaturedTrackGroupID uuid.UUID `bun:"type:uuid,default:uuid_nil()"` 55 56 // Members []UserGroup `pg:"many2many:user_group_members,fk:user_group_id,joinFK:member_id"` 57 // MemberOfGroups []UserGroup `pg:"many2many:user_group_members,fk:member_id,joinFK:user_group_id"` 58 59 //OwnerOfTracks []Track `pg:"fk:user_group_id"` // user group gets paid for these tracks 60 //ArtistOfTracks []uuid.UUID `bun:",type:uuid[]" pg:",array"` // user group displayed as artist for these tracks 61 //OwnerOfTrackGroups []TrackGroup `pg:"fk:user_group_id"` // user group owner of these track groups 62 //LabelOfTrackGroups []TrackGroup `pg:"fk:label_id"` // label of these track groups 63 64 // func (u *UserGroup) BeforeInsert(c context.Context, db orm.DB) error { 65 // newPrivacy := &UserGroupPrivacy{Private: false, OwnedTracks: true, SupportedArtists: true} 66 // _, pgerr := db.Model(newPrivacy).Returning("*").Insert() 67 // if pgerr != nil { 68 // return pgerr 69 // } 70 // u.PrivacyID = newPrivacy.ID 71 72 // return nil 73 // } 74 75 // Create creates a new UserGroup 76 // func (u *UserGroup) Create(db *pg.DB, userGroup *pbUser.UserGroup) (error, string) { 77 // var table string 78 // tx, err := db.Begin() 79 // if err != nil { 80 // return err, table 81 // } 82 // defer tx.Rollback() 83 84 // groupTaxonomy := new(GroupTaxonomy) 85 // pgerr := tx.Model(groupTaxonomy).Where("type = ?", userGroup.Type.Type).First() 86 87 // if pgerr != nil { 88 // return pgerr, "group_taxonomy" 89 // } 90 // u.TypeID = groupTaxonomy.ID 91 92 // var newAddress *StreetAddress 93 // if userGroup.Address != nil { 94 // newAddress = &StreetAddress{Data: userGroup.Address.Data} 95 // _, pgerr = tx.Model(newAddress).Returning("*").Insert() 96 // if pgerr != nil { 97 // return pgerr, "street_address" 98 // } 99 // } 100 // u.AddressID = newAddress.ID 101 102 // linkIDs, pgerr := getLinkIDs(userGroup.Links, tx) 103 // if pgerr != nil { 104 // return pgerr, "link" 105 // } 106 // u.Links = linkIDs 107 108 // // tagIDs, pgerr := GetTagIDs(userGroup.Tags, tx) 109 // // if pgerr != nil { 110 // // return pgerr, "tag" 111 // // } 112 // // u.Tags = tagIDs 113 114 // // recommendedArtistIDs, pgerr := GetRelatedUserGroupIDs(userGroup.RecommendedArtists, tx) 115 // // if pgerr != nil { 116 // // return pgerr, "user_group" 117 // // } 118 // // u.RecommendedArtists = recommendedArtistIDs 119 120 // _, pgerr = tx.Model(u).Returning("*").Insert() 121 // if pgerr != nil { 122 // fmt.Println("insert") 123 // return pgerr, "user_group" 124 // } 125 126 // // if len(recommendedArtistIDs) > 0 { 127 // // _, pgerr = tx.Exec(` 128 // // UPDATE user_groups 129 // // SET recommended_by = (select array_agg(distinct e) from unnest(recommended_by || ?) e) 130 // // WHERE id IN (?) 131 // // `, pg.Array([]uuid.UUID{u.ID}), pg.In(recommendedArtistIDs)) 132 // // if pgerr != nil { 133 // // return pgerr, "user_group" 134 // // } 135 // // } 136 137 // pgerr = tx.Model(u). 138 // Column("Privacy"). 139 // WherePK(). 140 // Select() 141 // if pgerr != nil { 142 // return pgerr, "user_group" 143 // } 144 145 // // Building response 146 // userGroup.Address.Id = u.AddressID.String() 147 // userGroup.Type.ID = u.TypeID.String() 148 // // userGroup.Privacy = &pbUser.Privacy{ 149 // // ID: u.Privacy.ID.String(), 150 // // Private: u.Privacy.Private, 151 // // OwnedTracks: u.Privacy.OwnedTracks, 152 // // SupportedArtists: u.Privacy.SupportedArtists, 153 // // } 154 155 // return tx.Commit(), table 156 // } 157 158 // func (u *UserGroup) Update(db *pg.DB, userGroup *pbUser.UserGroup) (error, string) { 159 // var table string 160 // tx, err := db.Begin() 161 // if err != nil { 162 // return err, "user_group" 163 // } 164 // defer tx.Rollback() 165 166 // userGroupToUpdate := &UserGroup{IDRecord: IDRecord{ID: u.ID}} 167 // pgerr := tx.Model(userGroupToUpdate). 168 // Column("user_group.links", "Type"). 169 // WherePK(). 170 // Select() 171 // if pgerr != nil { 172 // return pgerr, "user_group" 173 // } 174 175 // columns := []string{ 176 // "updated_at", 177 // // "pro", 178 // // "publisher", 179 // "links", 180 // // "tags", 181 // "display_name", 182 // // "avatar", 183 // "description", 184 // "short_bio", 185 // // "banner", 186 // "group_email_address", 187 // } 188 189 // // User group type - changes allowed: user => artist, user => label 190 // groupTaxonomy := new(GroupTaxonomy) 191 // pgerr = tx.Model(groupTaxonomy).Where("type = ?", userGroup.Type.Type).First() 192 // if pgerr != nil { 193 // return pgerr, "group_taxonomy" 194 // } 195 // if userGroupToUpdate.Type.ID != groupTaxonomy.ID { 196 // if userGroupToUpdate.Type.Type != "user" || 197 // (userGroupToUpdate.Type.Type == "user" && !(groupTaxonomy.Type == "artist" || groupTaxonomy.Type == "label")) { 198 // twerr := twirp.InvalidArgumentError("type", "not allowed") 199 // return twerr.(error), "user_group" 200 // } 201 // u.TypeID = groupTaxonomy.ID 202 // columns = append(columns, "type_id") 203 // } 204 205 // // Update address 206 // addressID, twerr := uuid.Parse(userGroup.Address.Id) 207 // if twerr != nil { 208 // return twerr, "street_address" 209 // } 210 // address := &StreetAddress{ID: addressID, Data: userGroup.Address.Data} 211 // _, pgerr = tx.Model(address).Column("data").WherePK().Update() 212 // // _, pgerr := db.Model(address).Set("data = ?", pg.Hstore(userGroup.Address.Data)).Where("id = ?id").Update() 213 // if pgerr != nil { 214 // return pgerr, "street_address" 215 // } 216 217 // // Update privacy 218 // // privacyID, twerr := uuidpkg.GetUUIDFromString(userGroup.Privacy.ID) 219 // // if twerr != nil { 220 // // return twerr, "user_group_privacy" 221 // // } 222 // // privacy := &UserGroupPrivacy{ 223 // // ID: privacyID, 224 // // Private: userGroup.Privacy.Private, 225 // // OwnedTracks: userGroup.Privacy.OwnedTracks, 226 // // SupportedArtists: userGroup.Privacy.SupportedArtists, 227 // // } 228 // // _, pgerr = tx.Model(privacy).WherePK().Returning("*").UpdateNotNull() 229 // // if pgerr != nil { 230 // // return pgerr, "user_group_privacy" 231 // // } 232 233 // // Update tags 234 // // tagIDs, pgerr := GetTagIDs(userGroup.Tags, tx) 235 // // if pgerr != nil { 236 // // return pgerr, "tag" 237 // // } 238 239 // // Update links 240 // linkIDs, pgerr := getLinkIDs(userGroup.Links, tx) 241 // if pgerr != nil { 242 // return pgerr, "link" 243 // } 244 // // Delete links if needed 245 // linkIDsToDelete := uuidpkg.Difference(userGroupToUpdate.Links, linkIDs) 246 // if len(linkIDsToDelete) > 0 { 247 // _, pgerr = tx.Model((*Link)(nil)). 248 // Where("id in (?)", pg.In(linkIDsToDelete)). 249 // Delete() 250 // if pgerr != nil { 251 // return pgerr, "link" 252 // } 253 // } 254 255 // // Update user group 256 // // u.Tags = tagIDs 257 // u.Links = linkIDs 258 // // u.RecommendedArtists = recommendedArtistIDs 259 // u.UpdatedAt = time.Now() 260 // _, pgerr = tx.Model(u). 261 // Column(columns...). 262 // WherePK(). 263 // Returning("*"). 264 // Update() 265 // if pgerr != nil { 266 // return pgerr, "user_group" 267 // } 268 269 // return tx.Commit(), table 270 // } 271 272 // func SearchUserGroups(query string, db *pg.DB) (*pbUser.SearchResults, twirp.Error) { 273 // var userGroups []UserGroup 274 275 // pgerr := db.Model(&userGroups). 276 // Column("user_group.id", "user_group.display_name", "user_group.avatar", "Privacy", "Type"). 277 // Where("to_tsvector('english'::regconfig, COALESCE(display_name, '') || ' ' || COALESCE(f_arr2str(tags), '')) @@ (plainto_tsquery('english'::regconfig, ?)) = true", query). 278 // Where("privacy.private = false"). 279 // Select() 280 // if pgerr != nil { 281 // return nil, errorpkg.CheckError(pgerr, "user_group") 282 // } 283 284 // var people []*pbUser.RelatedUserGroup 285 // var artists []*pbUser.RelatedUserGroup 286 // var labels []*pbUser.RelatedUserGroup 287 // for _, userGroup := range userGroups { 288 // searchUserGroup := &pbUser.RelatedUserGroup{ 289 // Id: userGroup.ID.String(), 290 // DisplayName: userGroup.DisplayName, 291 // // Avatar: userGroup.Avatar, 292 // } 293 // switch userGroup.TypeID { 294 // case "user": 295 // people = append(people, searchUserGroup) 296 // case "artist": 297 // artists = append(artists, searchUserGroup) 298 // case "label": 299 // labels = append(labels, searchUserGroup) 300 // } 301 // } 302 // return &pbUser.SearchResults{ 303 // People: people, 304 // Artists: artists, 305 // Labels: labels, 306 // }, nil 307 // } 308 309 // func (u *UserGroup) Delete(tx *pg.Tx) (error, string) { 310 // pgerr := tx.Model(u). 311 // Column("user_group.links", "user_group.followers", "user_group.recommended_by", "user_group.recommended_artists", "Address", "Privacy", 312 // "OwnerOfTrackGroups", "LabelOfTrackGroups", "user_group.artist_of_tracks"). 313 // WherePK(). 314 // Select() 315 // if pgerr != nil { 316 // return pgerr, "user_group" 317 // } 318 319 // // These tracks contain the user group to delete as artist 320 // // so we have to remove it from the tracks' artists list 321 // // if len(u.ArtistOfTracks) > 0 { 322 // // _, pgerr = tx.Exec(` 323 // // UPDATE tracks 324 // // SET artists = array_remove(artists, ?) 325 // // WHERE id IN (?) 326 // // `, u.ID, pg.In(u.ArtistOfTracks)) 327 // // if pgerr != nil { 328 // // return pgerr, "track" 329 // // } 330 // // } 331 332 // // These track groups contain the user group to delete as label 333 // // so we have to set their label_id as null 334 // // if len(u.LabelOfTrackGroups) > 0 { 335 // // _, pgerr = tx.Model(&u.LabelOfTrackGroups). 336 // // Set("label_id = uuid_nil()"). 337 // // Update() 338 // // if pgerr != nil { 339 // // return pgerr, "track" 340 // // } 341 // // } 342 343 // // Delete track groups owned by user group to delete 344 // // if a track is a release (lp, ep, single), its tracks are owned by the same user group 345 // // and they'll be deleted as well 346 // // for _, trackGroup := range u.OwnerOfTrackGroups { 347 // // pgerr, table := trackGroup.Delete(tx) 348 // // if pgerr != nil { 349 // // return pgerr, table 350 // // } 351 // // } 352 353 // if len(u.Links) > 0 { 354 // _, pgerr = tx.Model((*Link)(nil)). 355 // Where("id in (?)", pg.In(u.Links)). 356 // Delete() 357 // if pgerr != nil { 358 // return pgerr, "link" 359 // } 360 // } 361 362 // if len(u.RecommendedBy) > 0 { 363 // _, pgerr = tx.Exec(` 364 // UPDATE user_groups 365 // SET recommended_artists = array_remove(recommended_artists, ?) 366 // WHERE id IN (?) 367 // `, u.ID, pg.In(u.RecommendedBy)) 368 // if pgerr != nil { 369 // return pgerr, "user_group" 370 // } 371 // } 372 373 // if len(u.RecommendedArtists) > 0 { 374 // _, pgerr = tx.Exec(` 375 // UPDATE user_groups 376 // SET recommended_by = array_remove(recommended_by, ?) 377 // WHERE id IN (?) 378 // `, u.ID, pg.In(u.RecommendedArtists)) 379 // if pgerr != nil { 380 // return pgerr, "user_group" 381 // } 382 // } 383 384 // if len(u.Followers) > 0 { 385 // _, pgerr = tx.Exec(` 386 // UPDATE users 387 // SET followed_groups = array_remove(followed_groups, ?) 388 // WHERE id IN (?) 389 // `, u.ID, pg.In(u.Followers)) 390 // if pgerr != nil { 391 // return pgerr, "user" 392 // } 393 // } 394 395 // var userGroupMembers []UserGroupMember 396 // _, pgerr = tx.Model(&userGroupMembers). 397 // Where("user_group_id = ?", u.ID). 398 // WhereOr("member_id = ?", u.ID). 399 // Delete() 400 // if pgerr != nil { 401 // return pgerr, "user_group_member" 402 // } 403 404 // _, pgerr = tx.Model(u).WherePK().Delete() 405 // if pgerr != nil { 406 // return pgerr, "user_group" 407 // } 408 409 // _, pgerr = tx.Model(u.Address).WherePK().Delete() 410 // if pgerr != nil { 411 // return pgerr, "street_address" 412 // } 413 414 // // _, pgerr = tx.Model(u.Privacy).WherePK().Delete() 415 // // if pgerr != nil { 416 // // return pgerr, "user_group_privacy" 417 // // } 418 419 // return nil, "" 420 // } 421 422 func (u *UserGroup) AddRecommended(db *pg.DB, recommendedID uuid.UUID) (error, string) { 423 var table string 424 tx, err := db.Begin() 425 if err != nil { 426 return err, table 427 } 428 defer tx.Rollback() 429 430 res, pgerr := tx.Exec(` 431 UPDATE user_groups 432 SET recommended_artists = (select array_agg(distinct e) from unnest(recommended_artists || ?) e) 433 WHERE id = ? 434 `, pg.Array([]uuid.UUID{recommendedID}), u.ID) 435 if res.RowsAffected() == 0 { 436 return pg.ErrNoRows, "user_group" 437 } 438 if pgerr != nil { 439 return pgerr, "user_group" 440 } 441 442 res, pgerr = tx.Exec(` 443 UPDATE user_groups 444 SET recommended_by = (select array_agg(distinct e) from unnest(recommended_by || ?) e) 445 WHERE id = ? 446 `, pg.Array([]uuid.UUID{u.ID}), recommendedID) 447 if res.RowsAffected() == 0 { 448 return pg.ErrNoRows, "recommended" 449 } 450 if pgerr != nil { 451 return pgerr, "user_group" 452 } 453 454 return tx.Commit(), table 455 } 456 457 func (u *UserGroup) RemoveRecommended(db *pg.DB, recommendedID uuid.UUID) (error, string) { 458 var table string 459 tx, err := db.Begin() 460 if err != nil { 461 return err, table 462 } 463 defer tx.Rollback() 464 465 res, pgerr := tx.Exec(` 466 UPDATE user_groups 467 SET recommended_artists = array_remove(recommended_artists, ?) 468 WHERE id = ? 469 `, recommendedID, u.ID) 470 if res.RowsAffected() == 0 { 471 return pg.ErrNoRows, "user_group" 472 } 473 if pgerr != nil { 474 return pgerr, "user_group" 475 } 476 477 res, pgerr = tx.Exec(` 478 UPDATE user_groups 479 SET recommended_by = array_remove(recommended_by, ?) 480 WHERE id = ? 481 `, u.ID, recommendedID) 482 if res.RowsAffected() == 0 { 483 return pg.ErrNoRows, "recommended" 484 } 485 if pgerr != nil { 486 return pgerr, "user_group" 487 } 488 489 return tx.Commit(), table 490 } 491 492 // Select user groups in db with given 'ids' 493 // Return slice of UserGroup response 494 func GetRelatedUserGroups(ids []uuid.UUID, db orm.DB) ([]*pbUser.RelatedUserGroup, error) { 495 groupsResponse := make([]*pbUser.RelatedUserGroup, len(ids)) 496 if len(ids) > 0 { 497 var groups []UserGroup 498 pgerr := db.Model(&groups). 499 Where("id in (?)", pg.In(ids)). 500 Select() 501 if pgerr != nil { 502 return nil, pgerr 503 } 504 for i, group := range groups { 505 groupsResponse[i] = &pbUser.RelatedUserGroup{ 506 Id: group.ID.String(), 507 DisplayName: group.DisplayName, 508 // Avatar: group.Avatar, 509 } 510 } 511 } 512 513 return groupsResponse, nil 514 } 515 516 // Select user groups in db with given ids in 'userGroups' 517 // Return ids slice 518 // Used in CreateUserGroup/UpdateUserGroup to add/update ids slice to recommended Artists 519 func GetRelatedUserGroupIDs(userGroups []*pbUser.RelatedUserGroup, db *pg.Tx) ([]uuid.UUID, error) { 520 relatedUserGroups := make([]*UserGroup, len(userGroups)) 521 relatedUserGroupIDs := make([]uuid.UUID, len(userGroups)) 522 for i, userGroup := range userGroups { 523 id, twerr := uuid.Parse(userGroup.Id) 524 if twerr != nil { 525 return nil, twerr.(error) 526 } 527 relatedUserGroups[i] = &UserGroup{IDRecord: IDRecord{ID: id}} 528 pgerr := db.Model(relatedUserGroups[i]). 529 WherePK(). 530 Returning("id", "display_name", "avatar"). 531 Select() 532 if pgerr != nil { 533 return nil, pgerr 534 } 535 userGroup.DisplayName = relatedUserGroups[i].DisplayName 536 // userGroup.Avatar = relatedUserGroups[i].Avatar 537 relatedUserGroupIDs[i] = relatedUserGroups[i].ID 538 } 539 return relatedUserGroupIDs, nil 540 } 541 542 func getLinkIDs(l []*pbUser.Link, db *pg.Tx) ([]uuid.UUID, error) { 543 links := make([]*Link, len(l)) 544 linkIDs := make([]uuid.UUID, len(l)) 545 for i, link := range l { 546 if link.ID == "" { 547 links[i] = &Link{Platform: link.Platform, URI: link.Uri} 548 _, pgerr := db.Model(links[i]).Returning("*").Insert() 549 if pgerr != nil { 550 return nil, pgerr 551 } 552 linkIDs[i] = links[i].ID 553 link.ID = links[i].ID.String() 554 } else { 555 linkID, twerr := uuid.Parse(link.ID) 556 if twerr != nil { 557 return nil, twerr.(error) 558 } 559 linkIDs[i] = linkID 560 } 561 } 562 return linkIDs, nil 563 } 564 565 /*type TrackAnalytics struct { 566 ID uuid.UUID 567 Title string 568 PaidPlays int32 569 FreePlays int32 570 TotalCredits float32 571 } 572 573 // DEPRECATED - moved to Payment API 574 func (u *UserGroup) GetUserGroupTrackAnalytics(db *pg.DB) ([]*pbUser.TrackAnalytics, twirp.Error) { 575 pgerr := db.Model(u). 576 Column("OwnerOfTracks"). 577 WherePK(). 578 Select() 579 if pgerr != nil { 580 return nil, errorpkg.CheckError(pgerr, "user_group") 581 } 582 tracks := make([]TrackAnalytics, len(u.OwnerOfTracks)) 583 trackIDs := make([]uuid.UUID, len(u.OwnerOfTracks)) 584 for i, track := range(u.OwnerOfTracks) { 585 tracks[i] = TrackAnalytics{ 586 Title: track.Title, 587 } 588 trackIDs[i] = track.ID 589 } 590 artistTrackAnalytics := make([]*pbUser.TrackAnalytics, len(tracks)) 591 592 if len(u.OwnerOfTracks) > 0 { 593 _, pgerr := db.Query(&tracks, ` 594 SELECT play.track_id AS id, 595 count(case when play.type = 'paid' then 1 else null end) AS paid_plays, 596 count(case when play.type = 'free' then 1 else null end) AS free_plays, 597 SUM(play.credits) AS total_credits 598 FROM plays AS play 599 WHERE play.track_id IN (?) 600 GROUP BY play.track_id 601 `, pg.In(trackIDs)) 602 if pgerr != nil { 603 return nil, errorpkg.CheckError(pgerr, "play") 604 } 605 for i, track := range(tracks) { 606 artistTrackAnalytics[i] = &pbUser.TrackAnalytics{ 607 ID: track.ID.String(), 608 Title: track.Title, 609 TotalPlays: track.PaidPlays + track.FreePlays, 610 PaidPlays: track.PaidPlays, 611 FreePlays: track.FreePlays, 612 TotalCredits: float32(track.TotalCredits), 613 UserGroupCredits: 0.7*float32(track.TotalCredits), 614 ResonateCredits: 0.3*float32(track.TotalCredits), 615 } 616 } 617 } 618 619 return artistTrackAnalytics, nil 620 }*/