Files
dev-service/pkg/datastore/gormstore/user.go
itzaname 4cfa2c7fff
All checks were successful
continuous-integration/drone/push Build is passing
Add user search
2026-02-25 23:36:53 -05:00

179 lines
5.0 KiB
Go

package gormstore
import (
"context"
"errors"
"git.itzana.me/StrafesNET/dev-service/pkg/datastore"
"git.itzana.me/StrafesNET/dev-service/pkg/model"
"gorm.io/gorm"
"strconv"
"time"
)
// CreateUser creates a new User in the database.
func (g *Gormstore) CreateUser(ctx context.Context, user *model.User) error {
if err := g.db.WithContext(ctx).Create(user).Error; err != nil {
return err
}
return g.loadUserWithAssociations(ctx, user.ID, user)
}
// GetUser retrieves a single User by ID.
func (g *Gormstore) GetUser(ctx context.Context, id uint64) (*model.User, error) {
var user model.User
if err := g.loadUserWithAssociations(ctx, id, &user); err != nil {
return nil, err
}
return &user, nil
}
// GetAllUsers retrieves users from the database using cursor-based pagination.
func (g *Gormstore) GetAllUsers(ctx context.Context, pagination *datastore.CursorPagination) ([]model.User, error) {
// Validate and set default limit
if pagination.Limit < 1 {
pagination.Limit = datastore.DefaultPageLimit
}
// Fetch one extra record to determine if there are more results
limit := pagination.Limit + 1
query := g.db.WithContext(ctx).
Preload("RateLimit").
Preload("Permissions", func(db *gorm.DB) *gorm.DB {
return db.Order("permissions.id asc")
})
// Apply cursor filter if provided
if pagination.Cursor != "" {
cursorID, err := decodeCursor(pagination.Cursor)
if err != nil {
return nil, err
}
query = query.Where("id > ?", cursorID)
}
var users []model.User
if err := query.Order("id ASC").Limit(limit).Find(&users).Error; err != nil {
return nil, err
}
// Handle pagination
pagination.HasMore = len(users) > pagination.Limit
if pagination.HasMore && len(users) > 0 {
pagination.NextCursor = encodeCursor(users[pagination.Limit-1].ID)
users = users[:pagination.Limit]
} else {
pagination.NextCursor = ""
}
// Add default permissions to each user
for i := range users {
if err := g.addDefaultPermissionsToUser(ctx, &users[i]); err != nil {
return nil, err
}
}
return users, nil
}
// SearchUsers searches users by ID (exact) or username (case-insensitive partial match).
func (g *Gormstore) SearchUsers(ctx context.Context, query string) ([]model.User, error) {
q := g.db.WithContext(ctx).
Preload("RateLimit").
Preload("Permissions", func(db *gorm.DB) *gorm.DB {
return db.Order("permissions.id asc")
})
if id, err := strconv.ParseUint(query, 10, 64); err == nil {
q = q.Where("id = ?", id)
} else {
q = q.Where("username ILIKE ?", "%"+query+"%")
}
var users []model.User
if err := q.Order("id ASC").Find(&users).Error; err != nil {
return nil, err
}
for i := range users {
if err := g.addDefaultPermissionsToUser(ctx, &users[i]); err != nil {
return nil, err
}
}
return users, nil
}
// UpdateUser updates an existing User in the database.
func (g *Gormstore) UpdateUser(ctx context.Context, user *model.User) error {
user.UpdatedAt = time.Now()
return g.db.WithContext(ctx).Save(user).Error
}
// DeleteUser deletes a User by ID from the database.
func (g *Gormstore) DeleteUser(ctx context.Context, id uint64) error {
result := g.db.WithContext(ctx).Delete(&model.User{}, id)
if result.RowsAffected == 0 {
return datastore.ErrNotExists
}
return result.Error
}
// AddPermissionToUser associates a Permission with a User by their IDs.
func (g *Gormstore) AddPermissionToUser(ctx context.Context, userID uint64, permissionID uint32) error {
return g.db.WithContext(ctx).Model(&model.User{ID: userID}).
Association("Permissions").
Append(&model.Permission{ID: permissionID})
}
// RemovePermissionFromUser removes the association between a Permission and a User by their IDs.
func (g *Gormstore) RemovePermissionFromUser(ctx context.Context, userID uint64, permissionID uint32) error {
return g.db.WithContext(ctx).Model(&model.User{ID: userID}).
Association("Permissions").
Delete(&model.Permission{ID: permissionID})
}
// loadUserWithAssociations loads a user with all associated data and adds default permissions.
func (g *Gormstore) loadUserWithAssociations(ctx context.Context, userID uint64, user *model.User) error {
result := g.db.WithContext(ctx).
Preload("RateLimit").
Preload("Permissions", func(db *gorm.DB) *gorm.DB {
return db.Order("permissions.id asc")
}).
First(user, userID)
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
return datastore.ErrNotExists
}
if result.Error != nil {
return result.Error
}
return g.addDefaultPermissionsToUser(ctx, user)
}
// addDefaultPermissionsToUser adds any missing default permissions to a user.
func (g *Gormstore) addDefaultPermissionsToUser(ctx context.Context, user *model.User) error {
defaultPerms, err := g.GetDefaultPermissions(ctx)
if err != nil {
return err
}
// Create a map of existing permission IDs for efficient lookup
existingPermIDs := make(map[uint32]bool)
for _, p := range user.Permissions {
existingPermIDs[p.ID] = true
}
// Add any missing default permissions
for _, dp := range defaultPerms {
if !existingPermIDs[dp.ID] {
user.Permissions = append(user.Permissions, dp)
}
}
return nil
}