add user role
This commit is contained in:
parent
2319019eb2
commit
d869d83d4b
@ -258,12 +258,15 @@ func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processor
|
|||||||
novuProvider := processor.NewNovuProvider(novuConfig)
|
novuProvider := processor.NewNovuProvider(novuConfig)
|
||||||
notificationProc := processor.NewNotificationProcessor(novuProvider, novuConfig.IncomingLetterWorkflowID)
|
notificationProc := processor.NewNotificationProcessor(novuProvider, novuConfig.IncomingLetterWorkflowID)
|
||||||
|
|
||||||
|
// Create user role processor
|
||||||
|
userRoleProc := processor.NewUserRoleProcessor(a.db)
|
||||||
|
|
||||||
// Create user processor with Novu integration
|
// Create user processor with Novu integration
|
||||||
userProc := processor.NewUserProcessor(repos.userRepo, repos.userProfileRepo)
|
userProc := processor.NewUserProcessor(repos.userRepo, repos.userProfileRepo, userRoleProc)
|
||||||
userProc.SetNovuProcessor(novuProc)
|
userProc.SetNovuProcessor(novuProc)
|
||||||
|
|
||||||
// Create cached user processor for auth middleware
|
// Create cached user processor for auth middleware
|
||||||
cachedUserProc := processor.NewCachedUserProcessor(repos.userRepo, repos.userProfileRepo)
|
cachedUserProc := processor.NewCachedUserProcessor(repos.userRepo, repos.userProfileRepo, userRoleProc)
|
||||||
|
|
||||||
// Create recipient processor
|
// Create recipient processor
|
||||||
recipientProc := processor.NewRecipientProcessor(
|
recipientProc := processor.NewRecipientProcessor(
|
||||||
|
|||||||
@ -7,13 +7,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type CreateUserRequest struct {
|
type CreateUserRequest struct {
|
||||||
OrganizationID uuid.UUID `json:"organization_id" validate:"required"`
|
|
||||||
OutletID *uuid.UUID `json:"outlet_id,omitempty"`
|
|
||||||
Name string `json:"name" validate:"required,min=1,max=255"`
|
Name string `json:"name" validate:"required,min=1,max=255"`
|
||||||
Email string `json:"email" validate:"required,email"`
|
Email string `json:"email" validate:"required,email"`
|
||||||
Password string `json:"password" validate:"required,min=6"`
|
Password string `json:"password" validate:"required,min=6"`
|
||||||
Role string `json:"role" validate:"required,oneof=admin manager cashier waiter"`
|
RoleID *uuid.UUID `json:"role_id,omitempty" validate:"required"`
|
||||||
Permissions map[string]interface{} `json:"permissions,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateUserRequest struct {
|
type UpdateUserRequest struct {
|
||||||
|
|||||||
@ -42,6 +42,7 @@ func (p *Permissions) Scan(value interface{}) error {
|
|||||||
type User struct {
|
type User struct {
|
||||||
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
|
||||||
Name string `gorm:"not null;size:255" json:"name" validate:"required,min=1,max=255"`
|
Name string `gorm:"not null;size:255" json:"name" validate:"required,min=1,max=255"`
|
||||||
|
Username string `gorm:"not null;size:255" json:"username" validate:"required,min=1,max=255"`
|
||||||
Email string `gorm:"uniqueIndex;not null;size:255" json:"email" validate:"required,email"`
|
Email string `gorm:"uniqueIndex;not null;size:255" json:"email" validate:"required,email"`
|
||||||
PasswordHash string `gorm:"not null;size:255" json:"-"`
|
PasswordHash string `gorm:"not null;size:255" json:"-"`
|
||||||
IsActive bool `gorm:"default:true" json:"is_active"`
|
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||||
|
|||||||
@ -48,7 +48,7 @@ func (h *UserHandler) CreateUser(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.FromContext(c).Infof("UserHandler::CreateUser -> Successfully created user = %+v", userResponse)
|
logger.FromContext(c).Infof("UserHandler::CreateUser -> Successfully created user = %+v", userResponse)
|
||||||
c.JSON(http.StatusCreated, userResponse)
|
c.JSON(http.StatusOK, contract.BuildSuccessResponse(userResponse))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UserHandler) UpdateUser(c *gin.Context) {
|
func (h *UserHandler) UpdateUser(c *gin.Context) {
|
||||||
|
|||||||
@ -26,7 +26,7 @@ type cacheEntry struct {
|
|||||||
expiresAt time.Time
|
expiresAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCachedUserProcessor(userRepo *repository.UserRepositoryImpl, profileRepo *repository.UserProfileRepository) *CachedUserProcessor {
|
func NewCachedUserProcessor(userRepo *repository.UserRepositoryImpl, profileRepo *repository.UserProfileRepository, userRoleProc UserRoleProcessor) *CachedUserProcessor {
|
||||||
return &CachedUserProcessor{
|
return &CachedUserProcessor{
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
profileRepo: profileRepo,
|
profileRepo: profileRepo,
|
||||||
|
|||||||
@ -2,6 +2,8 @@ package processor
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"eslogad-be/internal/appcontext"
|
||||||
|
"time"
|
||||||
|
|
||||||
"eslogad-be/internal/entities"
|
"eslogad-be/internal/entities"
|
||||||
"eslogad-be/internal/repository"
|
"eslogad-be/internal/repository"
|
||||||
@ -50,40 +52,52 @@ func (p *RecipientProcessorImpl) CreateDefaultRecipients(ctx context.Context, le
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *RecipientProcessorImpl) CreateRecipients(ctx context.Context, letterID uuid.UUID, departmentIDs []uuid.UUID) ([]entities.LetterIncomingRecipient, error) {
|
func (p *RecipientProcessorImpl) CreateRecipients(ctx context.Context, letterID uuid.UUID, departmentIDs []uuid.UUID) ([]entities.LetterIncomingRecipient, error) {
|
||||||
if len(departmentIDs) == 0 {
|
userCreatorDepartment := appcontext.FromGinContext(ctx).DepartmentID
|
||||||
return []entities.LetterIncomingRecipient{}, nil
|
departmentIDs = append(departmentIDs, userCreatorDepartment)
|
||||||
}
|
|
||||||
|
|
||||||
userMemberships, err := p.userDeptRepo.ListActiveByDepartmentIDs(ctx, departmentIDs)
|
userMemberships, err := p.userDeptRepo.ListActiveByDepartmentIDs(ctx, departmentIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
recipients := p.buildUniqueRecipients(letterID, userMemberships)
|
recipients := p.buildUniqueRecipients(letterID, userMemberships, userCreatorDepartment)
|
||||||
|
|
||||||
if len(recipients) > 0 {
|
|
||||||
if err := p.recipientRepo.CreateBulk(ctx, recipients); err != nil {
|
if err := p.recipientRepo.CreateBulk(ctx, recipients); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return recipients, nil
|
return recipients, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RecipientProcessorImpl) buildUniqueRecipients(letterID uuid.UUID, userMemberships []repository.UserDepartmentRow) []entities.LetterIncomingRecipient {
|
func (p *RecipientProcessorImpl) buildUniqueRecipients(letterID uuid.UUID, userMemberships []repository.UserDepartmentRow, userCreatorDepartment uuid.UUID) []entities.LetterIncomingRecipient {
|
||||||
var recipients []entities.LetterIncomingRecipient
|
var recipients []entities.LetterIncomingRecipient
|
||||||
userMap := make(map[string]bool)
|
userMap := make(map[string]bool)
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
for _, membership := range userMemberships {
|
for _, membership := range userMemberships {
|
||||||
userIDStr := membership.UserID.String()
|
userIDStr := membership.UserID.String()
|
||||||
|
|
||||||
if !userMap[userIDStr] {
|
if !userMap[userIDStr] {
|
||||||
|
userID := membership.UserID
|
||||||
|
departmentID := membership.DepartmentID
|
||||||
|
|
||||||
|
if userCreatorDepartment == membership.DepartmentID {
|
||||||
recipients = append(recipients, entities.LetterIncomingRecipient{
|
recipients = append(recipients, entities.LetterIncomingRecipient{
|
||||||
LetterID: letterID,
|
LetterID: letterID,
|
||||||
RecipientUserID: &membership.UserID,
|
RecipientUserID: &userID,
|
||||||
RecipientDepartmentID: &membership.DepartmentID,
|
RecipientDepartmentID: &departmentID,
|
||||||
|
Status: entities.RecipientStatusCompleted,
|
||||||
|
ReadAt: &now,
|
||||||
|
CompletedAt: &now,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
recipients = append(recipients, entities.LetterIncomingRecipient{
|
||||||
|
LetterID: letterID,
|
||||||
|
RecipientUserID: &userID,
|
||||||
|
RecipientDepartmentID: &departmentID,
|
||||||
Status: entities.RecipientStatusNew,
|
Status: entities.RecipientStatusNew,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
userMap[userIDStr] = true
|
userMap[userIDStr] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ type UserProcessorImpl struct {
|
|||||||
userRepo UserRepository
|
userRepo UserRepository
|
||||||
profileRepo UserProfileRepository
|
profileRepo UserProfileRepository
|
||||||
novuProcessor NovuProcessor
|
novuProcessor NovuProcessor
|
||||||
|
userRoleProc UserRoleProcessor
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserProfileRepository interface {
|
type UserProfileRepository interface {
|
||||||
@ -29,10 +30,12 @@ type UserProfileRepository interface {
|
|||||||
func NewUserProcessor(
|
func NewUserProcessor(
|
||||||
userRepo UserRepository,
|
userRepo UserRepository,
|
||||||
profileRepo UserProfileRepository,
|
profileRepo UserProfileRepository,
|
||||||
|
userRoleProc UserRoleProcessor,
|
||||||
) *UserProcessorImpl {
|
) *UserProcessorImpl {
|
||||||
return &UserProcessorImpl{
|
return &UserProcessorImpl{
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
profileRepo: profileRepo,
|
profileRepo: profileRepo,
|
||||||
|
userRoleProc: userRoleProc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +61,6 @@ func (p *UserProcessorImpl) CreateUser(ctx context.Context, req *contract.Create
|
|||||||
return nil, fmt.Errorf("failed to create user: %w", err)
|
return nil, fmt.Errorf("failed to create user: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// create default user profile
|
|
||||||
defaultFullName := userEntity.Name
|
defaultFullName := userEntity.Name
|
||||||
profile := &entities.UserProfile{
|
profile := &entities.UserProfile{
|
||||||
UserID: userEntity.ID,
|
UserID: userEntity.ID,
|
||||||
@ -70,11 +72,14 @@ func (p *UserProcessorImpl) CreateUser(ctx context.Context, req *contract.Create
|
|||||||
}
|
}
|
||||||
_ = p.profileRepo.Create(ctx, profile)
|
_ = p.profileRepo.Create(ctx, profile)
|
||||||
|
|
||||||
// Create Novu subscriber
|
if req.RoleID != nil {
|
||||||
|
if err := p.userRoleProc.AssignRoleToUser(ctx, userEntity.ID, *req.RoleID); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to assign role to user: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if p.novuProcessor != nil {
|
if p.novuProcessor != nil {
|
||||||
if err := p.novuProcessor.CreateSubscriber(ctx, userEntity); err != nil {
|
if err := p.novuProcessor.CreateSubscriber(ctx, userEntity); err != nil {
|
||||||
// Log error but don't fail user creation
|
|
||||||
// You might want to add proper logging here
|
|
||||||
_ = err
|
_ = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
97
internal/processor/user_role_processor.go
Normal file
97
internal/processor/user_role_processor.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package processor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"eslogad-be/internal/entities"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserRoleProcessor interface {
|
||||||
|
AssignRoleToUser(ctx context.Context, userID, roleID uuid.UUID) error
|
||||||
|
RemoveRoleFromUser(ctx context.Context, userID, roleID uuid.UUID) error
|
||||||
|
GetUserRoles(ctx context.Context, userID uuid.UUID) ([]entities.Role, error)
|
||||||
|
HasRole(ctx context.Context, userID uuid.UUID, roleCode string) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserRoleProcessorImpl struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUserRoleProcessor(db *gorm.DB) *UserRoleProcessorImpl {
|
||||||
|
return &UserRoleProcessorImpl{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserRoleEntry struct {
|
||||||
|
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()"`
|
||||||
|
UserID uuid.UUID `gorm:"type:uuid;not null"`
|
||||||
|
RoleID uuid.UUID `gorm:"type:uuid;not null"`
|
||||||
|
AssignedAt time.Time `gorm:"default:CURRENT_TIMESTAMP"`
|
||||||
|
RemovedAt *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (UserRoleEntry) TableName() string {
|
||||||
|
return "user_role"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *UserRoleProcessorImpl) AssignRoleToUser(ctx context.Context, userID, roleID uuid.UUID) error {
|
||||||
|
var existingEntry UserRoleEntry
|
||||||
|
err := p.db.WithContext(ctx).
|
||||||
|
Where("user_id = ? AND role_id = ? AND removed_at IS NULL", userID, roleID).
|
||||||
|
First(&existingEntry).Error
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != gorm.ErrRecordNotFound {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
newEntry := UserRoleEntry{
|
||||||
|
UserID: userID,
|
||||||
|
RoleID: roleID,
|
||||||
|
AssignedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.db.WithContext(ctx).Create(&newEntry).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *UserRoleProcessorImpl) RemoveRoleFromUser(ctx context.Context, userID, roleID uuid.UUID) error {
|
||||||
|
now := time.Now()
|
||||||
|
return p.db.WithContext(ctx).
|
||||||
|
Model(&UserRoleEntry{}).
|
||||||
|
Where("user_id = ? AND role_id = ? AND removed_at IS NULL", userID, roleID).
|
||||||
|
Update("removed_at", now).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *UserRoleProcessorImpl) GetUserRoles(ctx context.Context, userID uuid.UUID) ([]entities.Role, error) {
|
||||||
|
var roles []entities.Role
|
||||||
|
err := p.db.WithContext(ctx).
|
||||||
|
Table("roles as r").
|
||||||
|
Select("r.*").
|
||||||
|
Joins("JOIN user_role ur ON ur.role_id = r.id AND ur.removed_at IS NULL").
|
||||||
|
Where("ur.user_id = ?", userID).
|
||||||
|
Find(&roles).Error
|
||||||
|
return roles, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *UserRoleProcessorImpl) HasRole(ctx context.Context, userID uuid.UUID, roleCode string) (bool, error) {
|
||||||
|
var count int64
|
||||||
|
err := p.db.WithContext(ctx).
|
||||||
|
Table("user_role as ur").
|
||||||
|
Joins("JOIN roles r ON r.id = ur.role_id").
|
||||||
|
Where("ur.user_id = ? AND r.code = ? AND ur.removed_at IS NULL", userID, roleCode).
|
||||||
|
Count(&count).Error
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return count > 0, nil
|
||||||
|
}
|
||||||
@ -91,7 +91,6 @@ func (r *LetterIncomingRepository) List(ctx context.Context, filter ListIncoming
|
|||||||
needsGroupBy = true
|
needsGroupBy = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply is_read filter if UserID is provided
|
|
||||||
if filter.UserID != nil && filter.IsRead != nil {
|
if filter.UserID != nil && filter.IsRead != nil {
|
||||||
if !joinedRecipients {
|
if !joinedRecipients {
|
||||||
query = query.Joins("JOIN letter_incoming_recipients ON letter_incoming_recipients.letter_id = letters_incoming.id")
|
query = query.Joins("JOIN letter_incoming_recipients ON letter_incoming_recipients.letter_id = letters_incoming.id")
|
||||||
@ -107,25 +106,20 @@ func (r *LetterIncomingRepository) List(ctx context.Context, filter ListIncoming
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply is_dispositioned filter if DepartmentID is provided
|
|
||||||
if filter.DepartmentID != nil && filter.IsDispositioned != nil {
|
if filter.DepartmentID != nil && filter.IsDispositioned != nil {
|
||||||
query = query.Joins("LEFT JOIN letter_incoming_dispositions_department lidd ON lidd.letter_incoming_id = letters_incoming.id AND lidd.department_id = ?", *filter.DepartmentID)
|
query = query.Joins("LEFT JOIN letter_incoming_dispositions_department lidd ON lidd.letter_incoming_id = letters_incoming.id AND lidd.department_id = ?", *filter.DepartmentID)
|
||||||
|
|
||||||
if *filter.IsDispositioned {
|
if *filter.IsDispositioned {
|
||||||
// Has been dispositioned (status is not 'pending' or record exists with non-pending status)
|
|
||||||
query = query.Where("lidd.id IS NOT NULL AND lidd.status != 'pending'")
|
query = query.Where("lidd.id IS NOT NULL AND lidd.status != 'pending'")
|
||||||
} else {
|
} else {
|
||||||
// Not yet dispositioned (no record or status is 'pending')
|
|
||||||
query = query.Where("lidd.id IS NULL OR lidd.status = 'pending'")
|
query = query.Where("lidd.id IS NULL OR lidd.status = 'pending'")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply priority filter
|
|
||||||
if len(filter.PriorityIDs) > 0 {
|
if len(filter.PriorityIDs) > 0 {
|
||||||
query = query.Where("letters_incoming.priority_id IN ?", filter.PriorityIDs)
|
query = query.Where("letters_incoming.priority_id IN ?", filter.PriorityIDs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply is_archived filter based on recipient's is_archived field
|
|
||||||
if filter.IsArchived != nil {
|
if filter.IsArchived != nil {
|
||||||
if *filter.IsArchived {
|
if *filter.IsArchived {
|
||||||
query = query.Where("letter_incoming_recipients.is_archived = ?", true)
|
query = query.Where("letter_incoming_recipients.is_archived = ?", true)
|
||||||
@ -137,12 +131,12 @@ func (r *LetterIncomingRepository) List(ctx context.Context, filter ListIncoming
|
|||||||
if filter.Status != nil {
|
if filter.Status != nil {
|
||||||
query = query.Where("letters_incoming.status = ?", *filter.Status)
|
query = query.Where("letters_incoming.status = ?", *filter.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
if filter.Query != nil {
|
if filter.Query != nil {
|
||||||
q := "%" + *filter.Query + "%"
|
q := "%" + *filter.Query + "%"
|
||||||
query = query.Where("letters_incoming.subject ILIKE ? OR letters_incoming.reference_number ILIKE ?", q, q)
|
query = query.Where("letters_incoming.subject ILIKE ? OR letters_incoming.reference_number ILIKE ?", q, q)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use GROUP BY instead of DISTINCT to handle joins properly
|
|
||||||
if needsGroupBy {
|
if needsGroupBy {
|
||||||
query = query.Group("letters_incoming.id")
|
query = query.Group("letters_incoming.id")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ type HealthHandler interface {
|
|||||||
|
|
||||||
type UserHandler interface {
|
type UserHandler interface {
|
||||||
ListUsers(c *gin.Context)
|
ListUsers(c *gin.Context)
|
||||||
|
CreateUser(c *gin.Context)
|
||||||
GetProfile(c *gin.Context)
|
GetProfile(c *gin.Context)
|
||||||
GetUserProfile(c *gin.Context)
|
GetUserProfile(c *gin.Context)
|
||||||
UpdateProfile(c *gin.Context)
|
UpdateProfile(c *gin.Context)
|
||||||
|
|||||||
@ -92,11 +92,12 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
|
|||||||
users := v1.Group("/users")
|
users := v1.Group("/users")
|
||||||
users.Use(r.authMiddleware.RequireAuth())
|
users.Use(r.authMiddleware.RequireAuth())
|
||||||
{
|
{
|
||||||
|
users.POST("", r.userHandler.CreateUser)
|
||||||
users.GET("", r.userHandler.ListUsers)
|
users.GET("", r.userHandler.ListUsers)
|
||||||
users.GET("/profile", r.userHandler.GetProfile)
|
users.GET("/profile", r.userHandler.GetProfile)
|
||||||
users.GET("/:id/profile", r.userHandler.GetUserProfile)
|
users.GET("/:id/profile", r.userHandler.GetUserProfile)
|
||||||
users.PUT("/profile", r.userHandler.UpdateProfile)
|
users.PUT("/profile", r.userHandler.UpdateProfile)
|
||||||
users.PUT(":id/password", r.userHandler.ChangePassword)
|
users.PUT("/:id/password", r.userHandler.ChangePassword)
|
||||||
users.GET("/titles", r.userHandler.ListTitles)
|
users.GET("/titles", r.userHandler.ListTitles)
|
||||||
users.GET("/mention", r.userHandler.GetActiveUsersForMention)
|
users.GET("/mention", r.userHandler.GetActiveUsersForMention)
|
||||||
users.POST("/profile/avatar", r.fileHandler.UploadProfileAvatar)
|
users.POST("/profile/avatar", r.fileHandler.UploadProfileAvatar)
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"eslogad-be/internal/logger"
|
"eslogad-be/internal/logger"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"eslogad-be/internal/appcontext"
|
"eslogad-be/internal/appcontext"
|
||||||
@ -134,7 +135,6 @@ func (s *LetterServiceImpl) CreateIncomingLetter(ctx context.Context, req *contr
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send notifications to all recipients after successful creation
|
|
||||||
if s.notificationProcessor != nil && len(recipients) > 0 {
|
if s.notificationProcessor != nil && len(recipients) > 0 {
|
||||||
go s.sendLetterNotifications(context.Background(), result, recipients)
|
go s.sendLetterNotifications(context.Background(), result, recipients)
|
||||||
}
|
}
|
||||||
@ -239,19 +239,15 @@ func (s *LetterServiceImpl) addCreatorAsRecipient(ctx context.Context, letterID
|
|||||||
|
|
||||||
func (s *LetterServiceImpl) sendLetterNotifications(ctx context.Context, letter *contract.IncomingLetterResponse, recipients []entities.LetterIncomingRecipient) {
|
func (s *LetterServiceImpl) sendLetterNotifications(ctx context.Context, letter *contract.IncomingLetterResponse, recipients []entities.LetterIncomingRecipient) {
|
||||||
for _, recipient := range recipients {
|
for _, recipient := range recipients {
|
||||||
// Only send notification to user recipients (not department recipients)
|
if recipient.Status != "completed" {
|
||||||
// Also exclude the creator from receiving notifications
|
|
||||||
if recipient.RecipientUserID != nil && *recipient.RecipientUserID != letter.CreatedBy {
|
|
||||||
// Use description if available, otherwise use subject
|
|
||||||
err := s.notificationProcessor.SendIncomingLetterNotification(
|
err := s.notificationProcessor.SendIncomingLetterNotification(
|
||||||
ctx,
|
ctx,
|
||||||
letter.ID,
|
letter.ID,
|
||||||
*recipient.RecipientUserID,
|
*recipient.RecipientUserID,
|
||||||
"Surat Masuk",
|
"Surat Masuk",
|
||||||
letter.Subject)
|
fmt.Sprintf("%s: %s", letter.SenderInstitution.Name, letter.Subject))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log error but don't fail the entire operation
|
|
||||||
logger.FromContext(ctx).Error("failed to send notification", err)
|
logger.FromContext(ctx).Error("failed to send notification", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ func CreateUserRequestToEntity(req *contract.CreateUserRequest, passwordHash str
|
|||||||
return &entities.User{
|
return &entities.User{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Email: req.Email,
|
Email: req.Email,
|
||||||
|
Username: req.Email,
|
||||||
PasswordHash: passwordHash,
|
PasswordHash: passwordHash,
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,14 +37,10 @@ func (v *UserValidatorImpl) ValidateCreateUserRequest(req *contract.CreateUserRe
|
|||||||
return errors.New("password must be at least 6 characters"), constants.MalformedFieldErrorCode
|
return errors.New("password must be at least 6 characters"), constants.MalformedFieldErrorCode
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.TrimSpace(req.Role) == "" {
|
if req.RoleID == nil {
|
||||||
return errors.New("role is required"), constants.MissingFieldErrorCode
|
return errors.New("role is required"), constants.MissingFieldErrorCode
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isValidUserRole(req.Role) {
|
|
||||||
return errors.New("invalid user role"), constants.MalformedFieldErrorCode
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, ""
|
return nil, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user