Merge remote-tracking branch 'origin/feature/notification' into self-order+notification
# Conflicts: # go.mod # go.sum # internal/app/app.go # internal/router/router.go
This commit is contained in:
@@ -8,7 +8,9 @@ import (
|
||||
|
||||
"apskel-pos-be/config"
|
||||
"apskel-pos-be/internal/contract"
|
||||
"apskel-pos-be/internal/entities"
|
||||
"apskel-pos-be/internal/models"
|
||||
"apskel-pos-be/internal/processor"
|
||||
"apskel-pos-be/internal/transformer"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
@@ -24,11 +26,12 @@ type AuthService interface {
|
||||
}
|
||||
|
||||
type AuthServiceImpl struct {
|
||||
userProcessor UserProcessor
|
||||
jwtSecret string
|
||||
refreshSecret string
|
||||
tokenTTL time.Duration
|
||||
refreshTokenTTL time.Duration
|
||||
userProcessor UserProcessor
|
||||
userDeviceProcessor processor.UserDeviceProcessor
|
||||
jwtSecret string
|
||||
refreshSecret string
|
||||
tokenTTL time.Duration
|
||||
refreshTokenTTL time.Duration
|
||||
}
|
||||
|
||||
type Claims struct {
|
||||
@@ -39,13 +42,14 @@ type Claims struct {
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
func NewAuthService(userProcessor UserProcessor, authConfig *config.AuthConfig) AuthService {
|
||||
func NewAuthService(userProcessor UserProcessor, userDeviceProcessor processor.UserDeviceProcessor, authConfig *config.AuthConfig) AuthService {
|
||||
return &AuthServiceImpl{
|
||||
userProcessor: userProcessor,
|
||||
jwtSecret: authConfig.AccessTokenSecret(),
|
||||
refreshSecret: authConfig.RefreshTokenSecret(),
|
||||
tokenTTL: authConfig.AccessTokenTTL(),
|
||||
refreshTokenTTL: authConfig.RefreshTokenTTL(),
|
||||
userProcessor: userProcessor,
|
||||
userDeviceProcessor: userDeviceProcessor,
|
||||
jwtSecret: authConfig.AccessTokenSecret(),
|
||||
refreshSecret: authConfig.RefreshTokenSecret(),
|
||||
tokenTTL: authConfig.AccessTokenTTL(),
|
||||
refreshTokenTTL: authConfig.RefreshTokenTTL(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +85,25 @@ func (s *AuthServiceImpl) Login(ctx context.Context, req *contract.LoginRequest)
|
||||
return nil, fmt.Errorf("failed to generate refresh token: %w", err)
|
||||
}
|
||||
|
||||
// Register or update device info if provided
|
||||
if req.DeviceID != "" && s.userDeviceProcessor != nil {
|
||||
deviceReq := &models.RegisterUserDeviceRequest{
|
||||
UserID: userResponse.ID,
|
||||
DeviceID: req.DeviceID,
|
||||
DeviceName: req.DeviceName,
|
||||
DeviceType: entities.DeviceType(req.DeviceType),
|
||||
Platform: entities.DevicePlatform(req.Platform),
|
||||
FCMToken: req.FCMToken,
|
||||
AppVersion: req.AppVersion,
|
||||
OsVersion: req.OsVersion,
|
||||
}
|
||||
// Non-blocking: log error but don't fail login
|
||||
if _, err := s.userDeviceProcessor.RegisterDevice(ctx, deviceReq); err != nil {
|
||||
// Log but don't fail the login
|
||||
_ = err
|
||||
}
|
||||
}
|
||||
|
||||
return &contract.LoginResponse{
|
||||
Token: token,
|
||||
RefreshToken: refreshToken,
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/contract"
|
||||
"apskel-pos-be/internal/models"
|
||||
"apskel-pos-be/internal/processor"
|
||||
"apskel-pos-be/internal/transformer"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type NotificationService interface {
|
||||
Send(ctx context.Context, req *contract.SendNotificationRequest, createdBy uuid.UUID) *contract.Response
|
||||
Broadcast(ctx context.Context, req *contract.BroadcastNotificationRequest, organizationID, createdBy uuid.UUID) *contract.Response
|
||||
MarkAsRead(ctx context.Context, receiverID, userID uuid.UUID) *contract.Response
|
||||
MarkAllAsRead(ctx context.Context, userID uuid.UUID) *contract.Response
|
||||
DeleteForUser(ctx context.Context, receiverID, userID uuid.UUID) *contract.Response
|
||||
ListForUser(ctx context.Context, req *contract.ListNotificationsRequest, userID uuid.UUID) *contract.Response
|
||||
GetByID(ctx context.Context, id uuid.UUID) *contract.Response
|
||||
}
|
||||
|
||||
type NotificationServiceImpl struct {
|
||||
notificationProcessor processor.NotificationProcessor
|
||||
}
|
||||
|
||||
func NewNotificationService(notificationProcessor processor.NotificationProcessor) *NotificationServiceImpl {
|
||||
return &NotificationServiceImpl{
|
||||
notificationProcessor: notificationProcessor,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *NotificationServiceImpl) Send(ctx context.Context, req *contract.SendNotificationRequest, createdBy uuid.UUID) *contract.Response {
|
||||
modelReq := &models.SendNotificationRequest{
|
||||
Title: req.Title,
|
||||
Body: req.Body,
|
||||
Type: req.Type,
|
||||
Category: req.Category,
|
||||
Priority: req.Priority,
|
||||
ImageURL: req.ImageURL,
|
||||
ActionURL: req.ActionURL,
|
||||
NotifiableType: req.NotifiableType,
|
||||
NotifiableID: req.NotifiableID,
|
||||
Data: req.Data,
|
||||
ReceiverIDs: req.ReceiverIDs,
|
||||
ScheduledAt: req.ScheduledAt,
|
||||
ExpiredAt: req.ExpiredAt,
|
||||
CreatedBy: &createdBy,
|
||||
}
|
||||
|
||||
resp, err := s.notificationProcessor.Send(ctx, modelReq)
|
||||
if err != nil {
|
||||
errResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.NotificationServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errResp})
|
||||
}
|
||||
|
||||
return contract.BuildSuccessResponse(transformer.NotificationModelResponseToContract(resp))
|
||||
}
|
||||
|
||||
func (s *NotificationServiceImpl) Broadcast(ctx context.Context, req *contract.BroadcastNotificationRequest, organizationID, createdBy uuid.UUID) *contract.Response {
|
||||
modelReq := &models.BroadcastNotificationRequest{
|
||||
Title: req.Title,
|
||||
Body: req.Body,
|
||||
Type: req.Type,
|
||||
Category: req.Category,
|
||||
Priority: req.Priority,
|
||||
ImageURL: req.ImageURL,
|
||||
ActionURL: req.ActionURL,
|
||||
NotifiableType: req.NotifiableType,
|
||||
NotifiableID: req.NotifiableID,
|
||||
Data: req.Data,
|
||||
OrganizationID: organizationID,
|
||||
ScheduledAt: req.ScheduledAt,
|
||||
ExpiredAt: req.ExpiredAt,
|
||||
CreatedBy: &createdBy,
|
||||
}
|
||||
|
||||
resp, err := s.notificationProcessor.Broadcast(ctx, modelReq)
|
||||
if err != nil {
|
||||
errResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.NotificationServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errResp})
|
||||
}
|
||||
|
||||
return contract.BuildSuccessResponse(transformer.NotificationModelResponseToContract(resp))
|
||||
}
|
||||
|
||||
func (s *NotificationServiceImpl) MarkAsRead(ctx context.Context, receiverID, userID uuid.UUID) *contract.Response {
|
||||
resp, err := s.notificationProcessor.MarkAsRead(ctx, receiverID, userID)
|
||||
if err != nil {
|
||||
errResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.NotificationServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errResp})
|
||||
}
|
||||
|
||||
return contract.BuildSuccessResponse(transformer.NotificationReceiverModelResponseToContract(resp))
|
||||
}
|
||||
|
||||
func (s *NotificationServiceImpl) MarkAllAsRead(ctx context.Context, userID uuid.UUID) *contract.Response {
|
||||
if err := s.notificationProcessor.MarkAllAsRead(ctx, userID); err != nil {
|
||||
errResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.NotificationServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errResp})
|
||||
}
|
||||
|
||||
return contract.BuildSuccessResponse(map[string]interface{}{"message": "All notifications marked as read"})
|
||||
}
|
||||
|
||||
func (s *NotificationServiceImpl) DeleteForUser(ctx context.Context, receiverID, userID uuid.UUID) *contract.Response {
|
||||
if err := s.notificationProcessor.DeleteForUser(ctx, receiverID, userID); err != nil {
|
||||
errResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.NotificationServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errResp})
|
||||
}
|
||||
|
||||
return contract.BuildSuccessResponse(map[string]interface{}{"message": "Notification deleted"})
|
||||
}
|
||||
|
||||
func (s *NotificationServiceImpl) ListForUser(ctx context.Context, req *contract.ListNotificationsRequest, userID uuid.UUID) *contract.Response {
|
||||
modelReq := &models.ListNotificationsRequest{
|
||||
Page: req.Page,
|
||||
Limit: req.Limit,
|
||||
UserID: userID,
|
||||
IsRead: req.IsRead,
|
||||
}
|
||||
|
||||
receivers, total, unreadCount, err := s.notificationProcessor.ListForUser(ctx, modelReq)
|
||||
if err != nil {
|
||||
errResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.NotificationServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errResp})
|
||||
}
|
||||
|
||||
totalPages := int(math.Ceil(float64(total) / float64(req.Limit)))
|
||||
|
||||
response := contract.ListNotificationsResponse{
|
||||
Notifications: transformer.NotificationReceiverModelResponsesToContracts(receivers),
|
||||
TotalCount: total,
|
||||
UnreadCount: unreadCount,
|
||||
Page: req.Page,
|
||||
Limit: req.Limit,
|
||||
TotalPages: totalPages,
|
||||
}
|
||||
|
||||
return contract.BuildSuccessResponse(response)
|
||||
}
|
||||
|
||||
func (s *NotificationServiceImpl) GetByID(ctx context.Context, id uuid.UUID) *contract.Response {
|
||||
resp, err := s.notificationProcessor.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
errResp := contract.NewResponseError(constants.NotFoundErrorCode, constants.NotificationServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errResp})
|
||||
}
|
||||
|
||||
return contract.BuildSuccessResponse(transformer.NotificationModelResponseToContract(resp))
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"apskel-pos-be/internal/constants"
|
||||
"apskel-pos-be/internal/contract"
|
||||
"apskel-pos-be/internal/processor"
|
||||
"apskel-pos-be/internal/transformer"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type UserDeviceService interface {
|
||||
RegisterDevice(ctx context.Context, userID uuid.UUID, req *contract.RegisterUserDeviceRequest) *contract.Response
|
||||
UpdateDevice(ctx context.Context, id uuid.UUID, req *contract.UpdateUserDeviceRequest) *contract.Response
|
||||
DeleteDevice(ctx context.Context, id uuid.UUID) *contract.Response
|
||||
GetDeviceByID(ctx context.Context, id uuid.UUID) *contract.Response
|
||||
GetDevicesByUserID(ctx context.Context, userID uuid.UUID) *contract.Response
|
||||
ListDevices(ctx context.Context, req *contract.ListUserDevicesRequest) *contract.Response
|
||||
}
|
||||
|
||||
type UserDeviceServiceImpl struct {
|
||||
userDeviceProcessor processor.UserDeviceProcessor
|
||||
}
|
||||
|
||||
func NewUserDeviceService(userDeviceProcessor processor.UserDeviceProcessor) *UserDeviceServiceImpl {
|
||||
return &UserDeviceServiceImpl{
|
||||
userDeviceProcessor: userDeviceProcessor,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *UserDeviceServiceImpl) RegisterDevice(ctx context.Context, userID uuid.UUID, req *contract.RegisterUserDeviceRequest) *contract.Response {
|
||||
modelReq := transformer.RegisterUserDeviceRequestToModel(req)
|
||||
modelReq.UserID = userID
|
||||
|
||||
deviceResponse, err := s.userDeviceProcessor.RegisterDevice(ctx, modelReq)
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.UserDeviceServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
}
|
||||
|
||||
contractResponse := transformer.UserDeviceModelResponseToResponse(deviceResponse)
|
||||
return contract.BuildSuccessResponse(contractResponse)
|
||||
}
|
||||
|
||||
func (s *UserDeviceServiceImpl) UpdateDevice(ctx context.Context, id uuid.UUID, req *contract.UpdateUserDeviceRequest) *contract.Response {
|
||||
modelReq := transformer.UpdateUserDeviceRequestToModel(req)
|
||||
|
||||
deviceResponse, err := s.userDeviceProcessor.UpdateDevice(ctx, id, modelReq)
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.UserDeviceServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
}
|
||||
|
||||
contractResponse := transformer.UserDeviceModelResponseToResponse(deviceResponse)
|
||||
return contract.BuildSuccessResponse(contractResponse)
|
||||
}
|
||||
|
||||
func (s *UserDeviceServiceImpl) DeleteDevice(ctx context.Context, id uuid.UUID) *contract.Response {
|
||||
err := s.userDeviceProcessor.DeleteDevice(ctx, id)
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.UserDeviceServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
}
|
||||
|
||||
return contract.BuildSuccessResponse(map[string]interface{}{
|
||||
"message": "Device deleted successfully",
|
||||
})
|
||||
}
|
||||
|
||||
func (s *UserDeviceServiceImpl) GetDeviceByID(ctx context.Context, id uuid.UUID) *contract.Response {
|
||||
deviceResponse, err := s.userDeviceProcessor.GetDeviceByID(ctx, id)
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.NotFoundErrorCode, constants.UserDeviceServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
}
|
||||
|
||||
contractResponse := transformer.UserDeviceModelResponseToResponse(deviceResponse)
|
||||
return contract.BuildSuccessResponse(contractResponse)
|
||||
}
|
||||
|
||||
func (s *UserDeviceServiceImpl) GetDevicesByUserID(ctx context.Context, userID uuid.UUID) *contract.Response {
|
||||
deviceResponses, err := s.userDeviceProcessor.GetDevicesByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.UserDeviceServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
}
|
||||
|
||||
contractResponses := transformer.UserDeviceModelResponsesToResponses(deviceResponses)
|
||||
return contract.BuildSuccessResponse(contractResponses)
|
||||
}
|
||||
|
||||
func (s *UserDeviceServiceImpl) ListDevices(ctx context.Context, req *contract.ListUserDevicesRequest) *contract.Response {
|
||||
modelReq := transformer.ListUserDevicesRequestToModel(req)
|
||||
|
||||
filters := make(map[string]interface{})
|
||||
if modelReq.UserID != "" {
|
||||
filters["user_id"] = modelReq.UserID
|
||||
}
|
||||
if modelReq.Platform != "" {
|
||||
filters["platform"] = modelReq.Platform
|
||||
}
|
||||
|
||||
devices, totalPages, err := s.userDeviceProcessor.ListDevices(ctx, filters, modelReq.Page, modelReq.Limit)
|
||||
if err != nil {
|
||||
errorResp := contract.NewResponseError(constants.InternalServerErrorCode, constants.UserDeviceServiceEntity, err.Error())
|
||||
return contract.BuildErrorResponse([]*contract.ResponseError{errorResp})
|
||||
}
|
||||
|
||||
contractResponses := transformer.UserDeviceModelResponsesToResponses(devices)
|
||||
|
||||
response := contract.ListUserDevicesResponse{
|
||||
Devices: contractResponses,
|
||||
TotalCount: len(contractResponses),
|
||||
Page: modelReq.Page,
|
||||
Limit: modelReq.Limit,
|
||||
TotalPages: totalPages,
|
||||
}
|
||||
|
||||
return contract.BuildSuccessResponse(response)
|
||||
}
|
||||
Reference in New Issue
Block a user