Skip to content

Commit

Permalink
Merge pull request #2484 from traPtitech/feet/bulk-group-api
Browse files Browse the repository at this point in the history
Feet/bulk group api
  • Loading branch information
kaitoyama authored Nov 9, 2024
2 parents 66808e5 + 885cc34 commit a695022
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 8 deletions.
31 changes: 30 additions & 1 deletion docs/v3-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1086,14 +1086,37 @@ paths:
content:
application/json:
schema:
$ref: '#/components/schemas/UserGroupMember'
oneOf:
- $ref: '#/components/schemas/UserGroupMember'
- $ref: '#/components/schemas/UserGroupMembers'
description: ''
tags:
- group
operationId: addUserGroupMember
description: |-
指定したグループにメンバーを追加します。
対象のユーザーグループの管理者権限が必要です。
delete:
summary: グループメンバーを一括削除
responses:
'204':
description: |-
No Content
グループから全てのユーザーが削除されました。
'403':
description: |-
Forbidden
ユーザーグループを操作する権限がありません。
'404':
description: |-
Not Found
ユーザーグループが見つかりません。
tags:
- group
operationId: removeUserGroupMembers
description: |-
指定したグループから全てのメンバーを削除します。
対象のユーザーグループの管理者権限が必要です。
'/groups/{groupId}/members/{userId}':
parameters:
- $ref: '#/components/parameters/groupIdInPath'
Expand Down Expand Up @@ -5137,6 +5160,12 @@ components:
required:
- id
- role
UserGroupMembers:
title: UserGroupMembers
type: array
description: ユーザーグループメンバーの配列
items:
$ref: '#/components/schemas/UserGroupMember'
UserStats:
title: UserStats
type: object
Expand Down
74 changes: 74 additions & 0 deletions repository/gorm/user_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,53 @@ func (repo *Repository) AddUserToGroup(userID, groupID uuid.UUID, role string) e
return nil
}

// AddUsersToGroup implements UserGroupRepository interface.
func (repo *Repository) AddUsersToGroup(users []model.UserGroupMember, groupID uuid.UUID) error {
if groupID == uuid.Nil {
return repository.ErrNilID
}

err := repo.db.Transaction(func(tx *gorm.DB) error {
var g model.UserGroup
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Preload("Members").First(&g, &model.UserGroup{ID: groupID}).Error; err != nil {
return convertError(err)
}
if err := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "group_id"}, {Name: "user_id"}},
DoUpdates: clause.AssignmentColumns([]string{"role"}),
}).Create(&users).Error; err != nil {
return convertError(err)
}

for _, user := range users {
if g.IsMember(user.UserID) {
repo.hub.Publish(hub.Message{
Name: event.UserGroupMemberUpdated,
Fields: hub.Fields{
"group_id": user.GroupID,
"user_id": user.UserID,
},
})
} else {
repo.hub.Publish(hub.Message{
Name: event.UserGroupMemberAdded,
Fields: hub.Fields{
"group_id": user.GroupID,
"user_id": user.UserID,
},
})
}
}

return tx.Model(&g).UpdateColumn("updated_at", time.Now()).Error
})
if err != nil {
return err
}

return nil
}

// RemoveUserFromGroup implements UserGroupRepository interface.
func (repo *Repository) RemoveUserFromGroup(userID, groupID uuid.UUID) error {
if userID == uuid.Nil || groupID == uuid.Nil {
Expand Down Expand Up @@ -266,6 +313,33 @@ func (repo *Repository) RemoveUserFromGroup(userID, groupID uuid.UUID) error {
return nil
}

// RemoveUsersFromGroup implements UserGroupRepository interface.
func (repo *Repository) RemoveUsersFromGroup(groupID uuid.UUID) error {
if groupID == uuid.Nil {
return repository.ErrNilID
}
err := repo.db.Transaction(func(tx *gorm.DB) error {
var removedUsers []model.UserGroupMember
if err := tx.Where("group_id = ?", groupID).Find(&model.UserGroupMember{}).Error; err != nil {
return err
}
for _, userID := range removedUsers {
repo.hub.Publish(hub.Message{
Name: event.UserGroupMemberRemoved,
Fields: hub.Fields{
"group_id": groupID,
"user_id": userID,
},
})
}
return nil
})
if err != nil {
return nil
}
return nil
}

// AddUserToGroupAdmin implements UserGroupRepository interface.
func (repo *Repository) AddUserToGroupAdmin(userID, groupID uuid.UUID) error {
if userID == uuid.Nil || groupID == uuid.Nil {
Expand Down
16 changes: 15 additions & 1 deletion repository/user_group.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,27 @@ type UserGroupRepository interface {
// 引数にuuid.Nilを指定した場合、ErrNilIDを返します。
// DBによるエラーを返すことがあります。
AddUserToGroup(userID, groupID uuid.UUID, role string) error
// AddUsersToGroup 指定したグループに指定した複数のユーザーを追加します
//
// 成功した、或いは既に追加されている場合、nilを返します。
// 存在しないグループの場合、ErrNotFoundを返します。
// 引数にuuid.Nilを指定した場合、ErrNilIDを返します。
// DBによるエラーを返すことがあります。
AddUsersToGroup(users []model.UserGroupMember, groupID uuid.UUID) error
// RemoveUserFromGroup 指定したグループから指定したユーザーを削除します
//
// 成功した、或いは既に居ない場合、nilを返します。
// 全員の追加が成功した、或いは既に追加されている場合、nilを返します。
// 存在しないグループの場合、ErrNotFoundを返します。
// 引数にuuid.Nilを指定した場合、ErrNilIDを返します。
// DBによるエラーを返すことがあります。
RemoveUserFromGroup(userID, groupID uuid.UUID) error
// RemoveUsersFromGroup 指定したグループから指定した複数のユーザーを削除します
//
// 全員の削除が成功した、或いは既に削除されている場合、nilを返します。
// 存在しないグループの場合、ErrNotFoundを返します。
// 引数にuuid.Nilを指定した場合、ErrNilIDを返します。
// DBによるエラーを返すことがあります。
RemoveUsersFromGroup(groupID uuid.UUID) error
// AddUserToGroupAdmin 指定したグループの管理者に指定したユーザーを追加します
//
// 成功した、或いは既に追加されている場合、nilを返します。
Expand Down
1 change: 1 addition & 0 deletions router/v3/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ func (h *Handlers) Setup(e *echo.Group) {
{
apiGroupsGIDMembers.GET("", h.GetUserGroupMembers, requires(permission.GetUserGroup))
apiGroupsGIDMembers.POST("", h.AddUserGroupMember, requiresGroupAdminPerm, requires(permission.EditUserGroup))
apiGroupsGIDMembers.DELETE("", h.RemoveUserGroupMembers, requiresGroupAdminPerm, requires(permission.EditUserGroup))
apiGroupsGIDMembersUID := apiGroupsGIDMembers.Group("/:userID", requiresGroupAdminPerm)
{
apiGroupsGIDMembersUID.PATCH("", h.EditUserGroupMember, requires(permission.EditUserGroup))
Expand Down
42 changes: 36 additions & 6 deletions router/v3/user_groups.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package v3

import (
"bytes"
"context"
"errors"
"io"
"net/http"

vd "github.com/go-ozzo/ozzo-validation/v4"
"github.com/gofrs/uuid"
"github.com/labstack/echo/v4"

"github.com/traPtitech/traQ/model"
"github.com/traPtitech/traQ/repository"
"github.com/traPtitech/traQ/router/consts"
"github.com/traPtitech/traQ/router/extension"
Expand Down Expand Up @@ -170,16 +174,34 @@ func (r PostUserGroupMemberRequest) ValidateWithContext(ctx context.Context) err
// AddUserGroupMember POST /groups/:groupID/members
func (h *Handlers) AddUserGroupMember(c echo.Context) error {
g := getParamGroup(c)

var req PostUserGroupMemberRequest
if err := bindAndValidate(c, &req); err != nil {
bodyBytes, err := io.ReadAll(c.Request().Body)
if err != nil {
return err
}

if err := h.Repo.AddUserToGroup(req.ID, g.ID, req.Role); err != nil {
var singleReq PostUserGroupMemberRequest
c.Request().Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
if err := bindAndValidate(c, &singleReq); err == nil {
if err := h.Repo.AddUserToGroup(singleReq.ID, g.ID, singleReq.Role); err != nil {
return herror.InternalServerError(err)
}
return c.NoContent(http.StatusNoContent)
}
var reqs []PostUserGroupMemberRequest
c.Request().Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
if err := bindAndValidate(c, &reqs); err != nil {
return err
}
if len(reqs) == 0 {
return herror.BadRequest(errors.New("no users provided"))
}
users := make([]model.UserGroupMember, len(reqs))
for i, req := range reqs {
users[i] = model.UserGroupMember{UserID: req.ID, Role: req.Role, GroupID: g.ID}
}
if err := h.Repo.AddUsersToGroup(users, g.ID); err != nil {
return herror.InternalServerError(err)
}

return c.NoContent(http.StatusNoContent)
}

Expand Down Expand Up @@ -220,14 +242,22 @@ func (h *Handlers) EditUserGroupMember(c echo.Context) error {
func (h *Handlers) RemoveUserGroupMember(c echo.Context) error {
userID := getParamAsUUID(c, consts.ParamUserID)
g := getParamGroup(c)

if err := h.Repo.RemoveUserFromGroup(userID, g.ID); err != nil {
return herror.InternalServerError(err)
}

return c.NoContent(http.StatusNoContent)
}

// RemoveUserGroupMembers DELETE /groups/:groupID/members
func (h *Handlers) RemoveUserGroupMembers(c echo.Context) error {
g := getParamGroup(c)
if err := h.Repo.RemoveUsersFromGroup(g.ID); err != nil {
return herror.InternalServerError(err)
}
return c.NoContent(http.StatusNoContent)
}

// GetUserGroupAdmins GET /groups/:groupID/admins
func (h *Handlers) GetUserGroupAdmins(c echo.Context) error {
return c.JSON(http.StatusOK, getParamGroup(c).AdminIDArray())
Expand Down

0 comments on commit a695022

Please sign in to comment.