Skip to content

Commit

Permalink
Merge branch 'develop' into 457-include-sender-post-info
Browse files Browse the repository at this point in the history
# Conflicts:
#	CHANGELOG.md
#	core/services.go
  • Loading branch information
mdryankov committed Jul 16, 2024
2 parents 78f9790 + cd478d3 commit 304a4b3
Show file tree
Hide file tree
Showing 18 changed files with 553 additions and 64 deletions.
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

## [1.46.1] - 2024-07-11
### Fixed
- Do not send polls and direct message notifications as muted when they are not [#477](https://github.com/rokwire/groups-building-block/issues/477)

## [1.46.0] - 2024-07-10
### Added
- Admin API for adding group members by NetIDs [#458](https://github.com/rokwire/groups-building-block/issues/458)

## [1.45.2] - 2024-07-01
### Fixed
- Update direct messages notification pattern [#475](https://github.com/rokwire/groups-building-block/issues/475)


## [1.45.1] - 2024-06-26
### Changed
- Use all_bbs_groups & get_aggregated-users permissions for BBs APIs (Additional change) [#473](https://github.com/rokwire/groups-building-block/issues/473)

## [1.45.0] - 2024-06-26
### Changed
- Use all_bbs_groups permissions for all BBs APIs [#473](https://github.com/rokwire/groups-building-block/issues/473)

## [1.44.0] - 2024-06-24
### Added
- Include sender, post content and action in group post notification body [#457](https://github.com/rokwire/groups-building-block/issues/457)
- Send post notification only to the creator of the post [#372](https://github.com/rokwire/groups-building-block/issues/372)
Expand Down
88 changes: 88 additions & 0 deletions core/admin_membership.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2022 Board of Trustees of the University of Illinois.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package core

import (
"groups/core/model"
"groups/driven/storage"
)

func (app *Application) adminAddGroupMemberships(clientID string, current *model.User, groupID string, membershipStatuses model.MembershipStatuses) error {
err := app.storage.PerformTransaction(func(context storage.TransactionContext) error {
membership, _ := app.storage.FindGroupMembershipWithContext(context, clientID, groupID, current.ID)

if membership != nil && membership.IsAdmin() {

group, err := app.storage.FindGroup(context, clientID, groupID, &current.ID)
if err != nil {
return err
}

netIDs := membershipStatuses.GetAllNetIDs()
netIDAccounts, err := app.corebb.GetAllCoreAccountsWithNetIDs(netIDs, &current.AppID, &current.OrgID)
if err != nil {
return err
}

existingMemberships, err := app.storage.FindGroupMembershipsWithContext(context, clientID, model.MembershipFilter{
GroupIDs: []string{groupID},
NetIDs: netIDs,
})
if err != nil {
return err
}

var memberships []model.GroupMembership
mapping := membershipStatuses.GetAllNetIDStatusMapping()
if len(netIDAccounts) > 0 {
for _, account := range netIDAccounts {
if status, ok := mapping[account.GetNetID()]; ok {
if existingMemberships.GetMembershipBy(func(membership model.GroupMembership) bool {
return membership.NetID == account.GetNetID()
}) == nil {
memberships = append(memberships, account.ToMembership(groupID, status))
}
}
}
if len(memberships) > 0 {
return app.storage.CreateMemberships(context, clientID, current, group, memberships)
}
}
}

return nil
})

return err
}

func (app *Application) adminDeleteMembershipsByID(clientID string, current *model.User, groupID string, accountIDs []string) error {

err := app.storage.PerformTransaction(func(context storage.TransactionContext) error {
membership, _ := app.storage.FindGroupMembershipWithContext(context, clientID, groupID, current.ID)

if membership != nil && membership.IsAdmin() {

err := app.storage.DeleteGroupMembershipsByAccountsIDs(app.logger, context, accountIDs)
if err != nil {
return err
}
}

return nil
})

return err
}
4 changes: 3 additions & 1 deletion core/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ type Application struct {

config *model.ApplicationConfig

Services Services //expose to the drivers adapters
Services Services
Admin Administration

storage Storage
notifications Notifications
Expand Down Expand Up @@ -328,6 +329,7 @@ func NewApplication(version string, build string, storage Storage, notifications

//add the drivers ports/interfaces
application.Services = &servicesImpl{app: &application}
application.Admin = &administrationImpl{app: &application}

return &application
}
19 changes: 16 additions & 3 deletions core/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ type Services interface {
DeletePendingMembership(clientID string, current *model.User, groupID string) error

// Group Notifications
SendGroupNotification(clientID string, notification model.GroupNotification) error
SendGroupNotification(clientID string, notification model.GroupNotification, predicate model.MutePreferencePredicate) error
GetResearchProfileUserCount(clientID string, current *model.User, researchProfile map[string]map[string][]string) (int64, error)

// Group Events
Expand Down Expand Up @@ -311,8 +311,8 @@ func (s *servicesImpl) DeleteMembership(clientID string, current *model.User, gr
return s.app.deleteMembership(clientID, current, groupID)
}

func (s *servicesImpl) SendGroupNotification(clientID string, notification model.GroupNotification) error {
return s.app.sendGroupNotification(clientID, notification)
func (s *servicesImpl) SendGroupNotification(clientID string, notification model.GroupNotification, predicate model.MutePreferencePredicate) error {
return s.app.sendGroupNotification(clientID, notification, predicate)
}

func (s *servicesImpl) GetResearchProfileUserCount(clientID string, current *model.User, researchProfile map[string]map[string][]string) (int64, error) {
Expand Down Expand Up @@ -361,12 +361,22 @@ func (s *servicesImpl) GetGroupCalendarEvents(clientID string, current *model.Us

// Administration exposes administration APIs for the driver adapters
type Administration interface {
AdminAddGroupMemberships(clientID string, current *model.User, groupID string, membershipStatuses model.MembershipStatuses) error
AdminDeleteMembershipsByID(clientID string, current *model.User, groupID string, accountIDs []string) error
}

type administrationImpl struct {
app *Application
}

func (s *administrationImpl) AdminAddGroupMemberships(clientID string, current *model.User, groupID string, membershipStatuses model.MembershipStatuses) error {
return s.app.adminAddGroupMemberships(clientID, current, groupID, membershipStatuses)
}

func (s *administrationImpl) AdminDeleteMembershipsByID(clientID string, current *model.User, groupID string, accountIDs []string) error {
return s.app.adminDeleteMembershipsByID(clientID, current, groupID, accountIDs)
}

// Storage is used by corebb to storage data - DB storage adapter, file storage adapter etc
type Storage interface {
RegisterStorageListener(listener storage.Listener)
Expand Down Expand Up @@ -439,8 +449,10 @@ type Storage interface {
FindGroupMembershipsWithContext(context storage.TransactionContext, clientID string, filter model.MembershipFilter) (model.MembershipCollection, error)

FindGroupMembership(clientID string, groupID string, userID string) (*model.GroupMembership, error)
FindGroupMembershipWithContext(context storage.TransactionContext, clientID string, groupID string, userID string) (*model.GroupMembership, error)
FindGroupMembershipByID(clientID string, id string) (*model.GroupMembership, error)
FindUserGroupMemberships(clientID string, userID string) (model.MembershipCollection, error)
FindUserGroupMembershipsWithContext(ctx storage.TransactionContext, clientID string, userID string) (model.MembershipCollection, error)
BulkUpdateGroupMembershipsByExternalID(clientID string, groupID string, saveOperations []storage.SingleMembershipOperation, updateGroupStats bool) error
SaveGroupMembershipByExternalID(clientID string, groupID string, externalID string, userID *string, status *string,
email *string, name *string, memberAnswers []model.MemberAnswer, syncID *string, updateGroupStats bool) (*model.GroupMembership, error)
Expand Down Expand Up @@ -500,6 +512,7 @@ type Core interface {
RetrieveCoreServices(serviceIDs []string) ([]model.CoreService, error)
GetAccounts(searchParams map[string]interface{}, appID *string, orgID *string, limit *int, offset *int) ([]model.CoreAccount, error)
GetAccountsWithIDs(ids []string, appID *string, orgID *string, limit *int, offset *int) ([]model.CoreAccount, error)
GetAllCoreAccountsWithNetIDs(netIDs []string, appID *string, orgID *string) ([]model.CoreAccount, error)
GetAllCoreAccountsWithExternalIDs(externalIDs []string, appID *string, orgID *string) ([]model.CoreAccount, error)
GetAccountsCount(searchParams map[string]interface{}, appID *string, orgID *string) (int64, error)
LoadDeletedMemberships() ([]model.DeletedUserData, error)
Expand Down
1 change: 1 addition & 0 deletions core/model/filters.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type MembershipFilter struct {
UserIDs []string `json:"user_ids"` // core user ids
ExternalID *string `json:"external_id"` // core user external id
NetID *string `json:"net_id"` // core user net id
NetIDs []string `json:"net_ids"` // core user net ids
Name *string `json:"name"` // member's name
Statuses []string `json:"statuses"` // lest of membership statuses
Offset *int64 `json:"offset"` // result offset
Expand Down
53 changes: 48 additions & 5 deletions core/model/group_membership.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ func (c *MembershipCollection) GetMembersByStatus(status string) []GroupMembersh
return members
}

// Evaluate membership mute policy. First result is canSend, second result is for muted
type mutePreferencePredicate = func(member GroupMembership) (bool, bool)
// MutePreferencePredicate Evaluates membership mute policy. First result is canSend, second result is for muted
type MutePreferencePredicate = func(member GroupMembership) (bool, bool) // @name MutePreferencePredicate

// GetMembersAsNotificationRecipients constructs all official members as notification recipients
func (c *MembershipCollection) GetMembersAsNotificationRecipients(predicate mutePreferencePredicate) []notifications.Recipient {
func (c *MembershipCollection) GetMembersAsNotificationRecipients(predicate MutePreferencePredicate) []notifications.Recipient {

recipients := []notifications.Recipient{}

Expand Down Expand Up @@ -154,8 +154,8 @@ func (m *GroupMembership) ApplyFromCoreAccountIfEmpty(user CoreAccount) {
if m.UserID == "" && user.ID != "" {
m.UserID = user.ID
}
if m.ExternalID == "" && user.GetExternalID() != nil {
m.ExternalID = *user.GetExternalID()
if m.ExternalID == "" && user.GetExternalID() != "" {
m.ExternalID = user.GetExternalID()
}
if m.Email == "" && user.Profile.Email != "" {
m.Email = user.Profile.Email
Expand Down Expand Up @@ -266,3 +266,46 @@ type NotificationsPreferences struct {
EventsMuted bool `json:"events_mute" bson:"events_mute"`
PollsMuted bool `json:"polls_mute" bson:"polls_mute"`
} // @name NotificationsPreferences

// MembershipStatuses list of membership statuses
type MembershipStatuses []MembershipStatus

// GetAllNetIDs returns all net ids
func (m MembershipStatuses) GetAllNetIDs() []string {
var list []string

for _, status := range m {
if status.IsValid() && status.NetID != "" {
list = append(list, status.NetID)
}
}

return list
}

// GetAllNetIDStatusMapping returns all netID to status mapping
func (m MembershipStatuses) GetAllNetIDStatusMapping() map[string]string {
mapping := map[string]string{}

for _, status := range m {
if status.IsValid() && status.NetID != "" {
mapping[status.NetID] = status.Status
}
}

return mapping
}

// MembershipStatus short membership status
type MembershipStatus struct {
NetID string `json:"net_id" bson:"net_id"`
Status string `json:"status" bson:"status"` //pending, member, admin, rejected
}

// IsValid Checks if the membership status is valid
func (m *MembershipStatus) IsValid() bool {
return (m.Status == "rejected" || m.Status == "pending" || m.Status == "member" || m.Status == "admin") &&
(len(m.NetID) > 0)
}

// @name MembershipStatus
26 changes: 20 additions & 6 deletions core/model/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,23 +122,23 @@ type CoreAccount struct {
}

// GetExternalID Gets the external id
func (c *CoreAccount) GetExternalID() *string {
func (c *CoreAccount) GetExternalID() string {
for _, auth := range c.AuthTypes {
if auth.Active && len(auth.Identifier) > 0 {
return &auth.Identifier
return auth.Identifier
}
}
return nil
return ""
}

// GetNetID Gets the external id
func (c *CoreAccount) GetNetID() *string {
func (c *CoreAccount) GetNetID() string {
for _, auth := range c.AuthTypes {
if auth.Active && len(auth.Identifier) > 0 {
return &auth.Params.User.SystemSpecific.PreferredUsername
return auth.Params.User.SystemSpecific.PreferredUsername
}
}
return nil
return ""
}

// GetFullName Builds the fullname
Expand All @@ -156,6 +156,20 @@ func (c *CoreAccount) GetFullName() string {
return name
}

// ToMembership Builds the fullname
func (c *CoreAccount) ToMembership(groupID, status string) GroupMembership {
return GroupMembership{
GroupID: groupID,
UserID: c.ID,
ExternalID: c.GetExternalID(),
Name: c.GetFullName(),
NetID: c.GetNetID(),
Email: c.Profile.Email,
Status: status,
DateCreated: time.Now(),
}
}

// DeletedUserData represents a user-deleted
type DeletedUserData struct {
AppID string `json:"app_id"`
Expand Down
22 changes: 12 additions & 10 deletions core/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,13 @@ func (app *Application) createGroup(clientID string, current *model.User, group
externalID := account.GetExternalID()
fullName := account.GetFullName()
netID := account.GetNetID()
if externalID != nil && fullName != "" && netID != nil && *netID != current.NetID {
if externalID != "" && fullName != "" && netID != "" && netID != current.NetID {
members = append(members, model.GroupMembership{
ClientID: clientID,
GroupID: group.ID,
UserID: account.ID,
ExternalID: *externalID,
NetID: *netID,
ExternalID: externalID,
NetID: netID,
Name: fullName,
Email: account.Profile.Email,
Status: membersConfig.Status,
Expand Down Expand Up @@ -532,9 +532,12 @@ func (app *Application) sendGroupNotificationForNewPost(clientID string, current
groupStr = "Research Project"
}
title := fmt.Sprintf("%s - %s", groupStr, group.Title)
operation := "posted"
if post.ParentID != nil {
operation = "replied"
operation := "messaged you"
if len(post.ToMembersList) == 0 {
operation = "posted"
if post.ParentID != nil {
operation = "replied"
}
}
if currentUserName == nil && currentUserID != nil {
coreUsers, err := app.corebb.GetAccountsWithIDs([]string{*currentUserID}, nil, nil, nil, nil)
Expand Down Expand Up @@ -725,7 +728,7 @@ func (app *Application) deletePost(clientID string, userID string, groupID strin
return app.storage.DeletePost(nil, clientID, userID, groupID, postID, force)
}

func (app *Application) sendGroupNotification(clientID string, notification model.GroupNotification) error {
func (app *Application) sendGroupNotification(clientID string, notification model.GroupNotification, predicate model.MutePreferencePredicate) error {
memberStatuses := notification.MemberStatuses
if len(memberStatuses) == 0 {
memberStatuses = []string{"admin", "member"}
Expand All @@ -741,9 +744,8 @@ func (app *Application) sendGroupNotification(clientID string, notification mode
return err
}

app.sendNotification(members.GetMembersAsNotificationRecipients(func(member model.GroupMembership) (bool, bool) {
return true, true // Should it be a separate notification preference?
}), notification.Topic, notification.Subject, notification.Body, notification.Data, app.config.AppID, app.config.OrgID)
recipients := members.GetMembersAsNotificationRecipients(predicate)
app.sendNotification(recipients, notification.Topic, notification.Subject, notification.Body, notification.Data, app.config.AppID, app.config.OrgID)

return nil
}
Expand Down
Loading

0 comments on commit 304a4b3

Please sign in to comment.