first commit
This commit is contained in:
313
backend/internal/application/services/admin_service.go
Normal file
313
backend/internal/application/services/admin_service.go
Normal file
@@ -0,0 +1,313 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
|
||||
"github.com/noteapp/backend/internal/application/dto"
|
||||
"github.com/noteapp/backend/internal/domain/entities"
|
||||
"github.com/noteapp/backend/internal/domain/repositories"
|
||||
)
|
||||
|
||||
// AdminService handles admin-level operations
|
||||
type AdminService struct {
|
||||
userRepo repositories.UserRepository
|
||||
groupRepo repositories.GroupRepository
|
||||
spaceRepo repositories.SpaceRepository
|
||||
membershipRepo repositories.MembershipRepository
|
||||
noteRepo repositories.NoteRepository
|
||||
categoryRepo repositories.CategoryRepository
|
||||
featureFlagRepo repositories.FeatureFlagRepository
|
||||
permissionService *PermissionService
|
||||
}
|
||||
|
||||
// NewAdminService creates a new AdminService
|
||||
func NewAdminService(
|
||||
userRepo repositories.UserRepository,
|
||||
groupRepo repositories.GroupRepository,
|
||||
spaceRepo repositories.SpaceRepository,
|
||||
membershipRepo repositories.MembershipRepository,
|
||||
noteRepo repositories.NoteRepository,
|
||||
categoryRepo repositories.CategoryRepository,
|
||||
featureFlagRepo repositories.FeatureFlagRepository,
|
||||
permissionService *PermissionService,
|
||||
) *AdminService {
|
||||
return &AdminService{
|
||||
userRepo: userRepo,
|
||||
groupRepo: groupRepo,
|
||||
spaceRepo: spaceRepo,
|
||||
membershipRepo: membershipRepo,
|
||||
noteRepo: noteRepo,
|
||||
categoryRepo: categoryRepo,
|
||||
featureFlagRepo: featureFlagRepo,
|
||||
permissionService: permissionService,
|
||||
}
|
||||
}
|
||||
|
||||
// ListUsers returns all users as admin DTOs
|
||||
func (s *AdminService) ListUsers(ctx context.Context) ([]*dto.AdminUserDTO, error) {
|
||||
users, err := s.userRepo.ListAllUsers(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]*dto.AdminUserDTO, len(users))
|
||||
for i, u := range users {
|
||||
if s.permissionService != nil {
|
||||
permissions, err := s.permissionService.GetUserEffectivePermissions(ctx, u)
|
||||
if err == nil {
|
||||
u.Permissions = permissions
|
||||
}
|
||||
}
|
||||
result[i] = dto.NewAdminUserDTO(u)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ListGroups returns all permission groups.
|
||||
func (s *AdminService) ListGroups(ctx context.Context) ([]*dto.PermissionGroupDTO, error) {
|
||||
groups, err := s.groupRepo.ListGroups(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*dto.PermissionGroupDTO, len(groups))
|
||||
for i, group := range groups {
|
||||
result[i] = dto.NewPermissionGroupDTO(group)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CreateGroup creates a new permission group.
|
||||
func (s *AdminService) CreateGroup(ctx context.Context, req *dto.CreatePermissionGroupRequest) (*dto.PermissionGroupDTO, error) {
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
return nil, errors.New("group name is required")
|
||||
}
|
||||
|
||||
group := &entities.PermissionGroup{
|
||||
Name: name,
|
||||
Description: strings.TrimSpace(req.Description),
|
||||
Permissions: normalizePermissions(req.Permissions),
|
||||
IsSystem: false,
|
||||
}
|
||||
|
||||
if err := s.groupRepo.CreateGroup(ctx, group); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewPermissionGroupDTO(group), nil
|
||||
}
|
||||
|
||||
// UpdateGroup updates a permission group.
|
||||
func (s *AdminService) UpdateGroup(ctx context.Context, groupID bson.ObjectID, req *dto.UpdatePermissionGroupRequest) (*dto.PermissionGroupDTO, error) {
|
||||
group, err := s.groupRepo.GetGroupByID(ctx, groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if group.IsSystem {
|
||||
return nil, errors.New("system groups cannot be modified")
|
||||
}
|
||||
|
||||
if name := strings.TrimSpace(req.Name); name != "" {
|
||||
group.Name = name
|
||||
}
|
||||
group.Description = strings.TrimSpace(req.Description)
|
||||
group.Permissions = normalizePermissions(req.Permissions)
|
||||
|
||||
if err := s.groupRepo.UpdateGroup(ctx, group); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.refreshAllUserPermissions(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewPermissionGroupDTO(group), nil
|
||||
}
|
||||
|
||||
// UpdateUserGroups assigns groups to a user.
|
||||
func (s *AdminService) UpdateUserGroups(ctx context.Context, userID bson.ObjectID, groupIDs []bson.ObjectID) (*dto.AdminUserDTO, error) {
|
||||
if s.permissionService == nil {
|
||||
return nil, errors.New("permission service unavailable")
|
||||
}
|
||||
|
||||
user, err := s.permissionService.SetUserGroups(ctx, userID, groupIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewAdminUserDTO(user), nil
|
||||
}
|
||||
|
||||
func (s *AdminService) refreshAllUserPermissions(ctx context.Context) error {
|
||||
if s.permissionService == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
users, err := s.userRepo.ListAllUsers(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, user := range users {
|
||||
if err := s.permissionService.UpdateUserEffectivePermissions(ctx, user); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func normalizePermissions(permissions []string) []string {
|
||||
unique := map[string]struct{}{}
|
||||
result := make([]string, 0, len(permissions))
|
||||
for _, permission := range permissions {
|
||||
normalized := entities.NormalizePermission(permission)
|
||||
if normalized == "" {
|
||||
continue
|
||||
}
|
||||
if _, exists := unique[normalized]; exists {
|
||||
continue
|
||||
}
|
||||
unique[normalized] = struct{}{}
|
||||
result = append(result, normalized)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ListAllSpaces returns all spaces
|
||||
func (s *AdminService) ListAllSpaces(ctx context.Context) ([]*dto.SpaceDTO, error) {
|
||||
spaces, err := s.spaceRepo.GetAllSpaces(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]*dto.SpaceDTO, len(spaces))
|
||||
for i, space := range spaces {
|
||||
result[i] = dto.NewSpaceDTO(space)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// UpdateSpace updates all editable space fields
|
||||
func (s *AdminService) UpdateSpace(ctx context.Context, spaceID bson.ObjectID, req *dto.CreateSpaceRequest) (*dto.SpaceDTO, error) {
|
||||
space, err := s.spaceRepo.GetSpaceByID(ctx, spaceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
space.Name = req.Name
|
||||
space.Description = req.Description
|
||||
space.Icon = req.Icon
|
||||
space.IsPublic = req.IsPublic
|
||||
|
||||
if err := s.spaceRepo.UpdateSpace(ctx, space); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewSpaceDTO(space), nil
|
||||
}
|
||||
|
||||
// SetSpaceVisibility sets the is_public flag on a space
|
||||
func (s *AdminService) SetSpaceVisibility(ctx context.Context, spaceID bson.ObjectID, isPublic bool) error {
|
||||
space, err := s.spaceRepo.GetSpaceByID(ctx, spaceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
space.IsPublic = isPublic
|
||||
return s.spaceRepo.UpdateSpace(ctx, space)
|
||||
}
|
||||
|
||||
// AddSpaceMember adds a member in a space if not already present.
|
||||
func (s *AdminService) AddSpaceMember(ctx context.Context, spaceID, userID bson.ObjectID) error {
|
||||
existing, err := s.membershipRepo.GetUserMembership(ctx, userID, spaceID)
|
||||
if err == nil && existing != nil {
|
||||
return nil
|
||||
}
|
||||
return s.membershipRepo.CreateMembership(ctx, &entities.Membership{
|
||||
UserID: userID,
|
||||
SpaceID: spaceID,
|
||||
})
|
||||
}
|
||||
|
||||
// ListSpaceMembers returns all members for a space
|
||||
func (s *AdminService) ListSpaceMembers(ctx context.Context, spaceID bson.ObjectID) ([]*dto.SpaceMemberDTO, error) {
|
||||
memberships, err := s.membershipRepo.GetSpaceMembers(ctx, spaceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*dto.SpaceMemberDTO, 0, len(memberships))
|
||||
for _, member := range memberships {
|
||||
username := member.UserID.Hex()
|
||||
if user, err := s.userRepo.GetUserByID(ctx, member.UserID); err == nil {
|
||||
username = user.Username
|
||||
}
|
||||
|
||||
result = append(result, &dto.SpaceMemberDTO{
|
||||
UserID: member.UserID.Hex(),
|
||||
Username: username,
|
||||
JoinedAt: member.JoinedAt.Format("2006-01-02T15:04:05Z"),
|
||||
})
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// RemoveSpaceMember removes a member from a space.
|
||||
func (s *AdminService) RemoveSpaceMember(ctx context.Context, spaceID, userID bson.ObjectID) error {
|
||||
membership, err := s.membershipRepo.GetUserMembership(ctx, userID, spaceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.membershipRepo.DeleteMembership(ctx, membership.ID)
|
||||
}
|
||||
|
||||
// DeleteSpace deletes a space and all associated data (admin, no permission check).
|
||||
func (s *AdminService) DeleteSpace(ctx context.Context, spaceID bson.ObjectID) error {
|
||||
if err := s.noteRepo.DeleteNotesBySpaceID(ctx, spaceID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.categoryRepo.DeleteCategoriesBySpaceID(ctx, spaceID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.membershipRepo.DeleteMembershipsBySpaceID(ctx, spaceID); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.spaceRepo.DeleteSpace(ctx, spaceID)
|
||||
}
|
||||
|
||||
// GetFeatureFlags returns current app-wide feature flags.
|
||||
func (s *AdminService) GetFeatureFlags(ctx context.Context) (*dto.FeatureFlagsDTO, error) {
|
||||
if s.featureFlagRepo == nil {
|
||||
return dto.NewFeatureFlagsDTO(nil), nil
|
||||
}
|
||||
|
||||
flags, err := s.featureFlagRepo.GetFeatureFlags(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewFeatureFlagsDTO(flags), nil
|
||||
}
|
||||
|
||||
// UpdateFeatureFlags updates app-wide feature flags.
|
||||
func (s *AdminService) UpdateFeatureFlags(ctx context.Context, req *dto.UpdateFeatureFlagsRequest) (*dto.FeatureFlagsDTO, error) {
|
||||
if s.featureFlagRepo == nil {
|
||||
return nil, errors.New("feature flags are unavailable")
|
||||
}
|
||||
|
||||
flags := &entities.FeatureFlags{
|
||||
RegistrationEnabled: req.RegistrationEnabled,
|
||||
ProviderLoginEnabled: req.ProviderLoginEnabled,
|
||||
PublicSharingEnabled: req.PublicSharingEnabled,
|
||||
}
|
||||
|
||||
if err := s.featureFlagRepo.UpdateFeatureFlags(ctx, flags); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewFeatureFlagsDTO(flags), nil
|
||||
}
|
||||
Reference in New Issue
Block a user