From dfcdef60d65d04a3c8afd0e430d70379af15c2ca Mon Sep 17 00:00:00 2001 From: "n.nakhostin" Date: Sat, 20 Sep 2025 10:11:36 +0330 Subject: [PATCH] Add Device Token Support for Customer and User Entities - Introduced a new DeviceToken field in both Customer and User entities to store device tokens for authentication and notifications. - Updated LoginForm structures for both Customer and User to include an optional DeviceToken field, allowing clients to send device tokens during login. - Enhanced the Login methods in the customer and user services to append the device token to the respective entities if it does not already exist, improving user session management and notification capabilities. - Ensured proper validation and structured responses for the new DeviceToken field in the forms, maintaining compliance with best practices for API design. --- cmd/web/docs/docs.go | 11 ++++++++++- cmd/web/docs/swagger.json | 11 ++++++++++- cmd/web/docs/swagger.yaml | 9 ++++++++- internal/customer/entity.go | 1 + internal/customer/form.go | 5 +++-- internal/customer/service.go | 6 ++++++ internal/user/entity.go | 1 + internal/user/form.go | 5 +++-- internal/user/service.go | 6 ++++++ 9 files changed, 48 insertions(+), 7 deletions(-) diff --git a/cmd/web/docs/docs.go b/cmd/web/docs/docs.go index 9be3aae..828c896 100644 --- a/cmd/web/docs/docs.go +++ b/cmd/web/docs/docs.go @@ -2057,7 +2057,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "Admin Flags" + "Admin-Flags" ], "summary": "Get flag by country code (Admin)", "parameters": [ @@ -7051,6 +7051,10 @@ const docTemplate = `{ "customer.LoginForm": { "type": "object", "properties": { + "device_token": { + "type": "string", + "example": "device_token" + }, "password": { "type": "string", "example": "App!1234" @@ -8077,6 +8081,11 @@ const docTemplate = `{ "user.LoginForm": { "type": "object", "properties": { + "device_token": { + "description": "Device token", + "type": "string", + "example": "device_token" + }, "password": { "description": "User password", "type": "string", diff --git a/cmd/web/docs/swagger.json b/cmd/web/docs/swagger.json index f652c5f..2528c5a 100644 --- a/cmd/web/docs/swagger.json +++ b/cmd/web/docs/swagger.json @@ -2051,7 +2051,7 @@ "application/json" ], "tags": [ - "Admin Flags" + "Admin-Flags" ], "summary": "Get flag by country code (Admin)", "parameters": [ @@ -7045,6 +7045,10 @@ "customer.LoginForm": { "type": "object", "properties": { + "device_token": { + "type": "string", + "example": "device_token" + }, "password": { "type": "string", "example": "App!1234" @@ -8071,6 +8075,11 @@ "user.LoginForm": { "type": "object", "properties": { + "device_token": { + "description": "Device token", + "type": "string", + "example": "device_token" + }, "password": { "description": "User password", "type": "string", diff --git a/cmd/web/docs/swagger.yaml b/cmd/web/docs/swagger.yaml index f76b758..c3d937d 100644 --- a/cmd/web/docs/swagger.yaml +++ b/cmd/web/docs/swagger.yaml @@ -406,6 +406,9 @@ definitions: type: object customer.LoginForm: properties: + device_token: + example: device_token + type: string password: example: App!1234 type: string @@ -1110,6 +1113,10 @@ definitions: type: object user.LoginForm: properties: + device_token: + description: Device token + example: device_token + type: string password: description: User password example: Admin!1234 @@ -2509,7 +2516,7 @@ paths: - BearerAuth: [] summary: Get flag by country code (Admin) tags: - - Admin Flags + - Admin-Flags /admin/v1/health: get: consumes: diff --git a/internal/customer/entity.go b/internal/customer/entity.go index bf636c4..37aeb02 100644 --- a/internal/customer/entity.go +++ b/internal/customer/entity.go @@ -45,6 +45,7 @@ type Customer struct { Role CustomerRole `bson:"role"` Phone *string `bson:"phone"` CompanyIDs []string `bson:"company_ids"` + DeviceToken []string `bson:"device_token"` LastLoginAt *int64 `bson:"last_login_at"` UpdatedAt int64 `bson:"updated_at"` CreatedAt int64 `bson:"created_at"` diff --git a/internal/customer/form.go b/internal/customer/form.go index 7ab105a..f072887 100644 --- a/internal/customer/form.go +++ b/internal/customer/form.go @@ -89,8 +89,9 @@ type CustomerListResponse struct { } type LoginForm struct { - Username string `json:"username" valid:"required" example:"app"` - Password string `json:"password" valid:"required" example:"App!1234"` + Username string `json:"username" valid:"required" example:"app"` + Password string `json:"password" valid:"required" example:"App!1234"` + DeviceToken string `json:"device_token" valid:"optional" example:"device_token"` } type RefreshTokenForm struct { diff --git a/internal/customer/service.go b/internal/customer/service.go index a744804..dc77816 100644 --- a/internal/customer/service.go +++ b/internal/customer/service.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "math/big" + "slices" "strings" "time" @@ -529,6 +530,11 @@ func (s *customerService) Login(ctx context.Context, form *LoginForm) (*AuthResp return nil, errors.New("failed to generate authentication tokens") } + // append device token to customer if not exists + if !slices.Contains(customer.DeviceToken, form.DeviceToken) { + customer.DeviceToken = append(customer.DeviceToken, form.DeviceToken) + } + err = s.repository.Login(ctx, customer.ID.Hex()) if err != nil { s.logger.Error("Failed to update customer last login", map[string]interface{}{ diff --git a/internal/user/entity.go b/internal/user/entity.go index f13c1e4..40d77ff 100644 --- a/internal/user/entity.go +++ b/internal/user/entity.go @@ -38,6 +38,7 @@ type User struct { Position *string `bson:"position"` Phone *string `bson:"phone"` ProfileImage *string `bson:"profile_image"` + DeviceToken []string `bson:"device_token"` IsVerified bool `bson:"is_verified"` LastLoginAt *int64 `bson:"last_login_at"` } diff --git a/internal/user/form.go b/internal/user/form.go index ef1ed9b..3569f31 100644 --- a/internal/user/form.go +++ b/internal/user/form.go @@ -33,8 +33,9 @@ type UpdateUserForm struct { // LoginForm represents the credentials required for user authentication type LoginForm struct { - Username string `json:"username" valid:"required" example:"admin"` // Username or email address - Password string `json:"password" valid:"required" example:"Admin!1234"` // User password + Username string `json:"username" valid:"required" example:"admin"` // Username or email address + Password string `json:"password" valid:"required" example:"Admin!1234"` // User password + DeviceToken string `json:"device_token" valid:"optional" example:"device_token"` // Device token } // RefreshTokenForm represents the refresh token required to generate new access tokens diff --git a/internal/user/service.go b/internal/user/service.go index cfe70cb..cd06c0a 100644 --- a/internal/user/service.go +++ b/internal/user/service.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "slices" "strings" "time" "tm/pkg/authorization" @@ -311,6 +312,11 @@ func (s *userService) Login(ctx context.Context, form *LoginForm) (*AuthResponse return nil, errors.New("invalid credentials") } + // append device token to user if not exists + if !slices.Contains(user.DeviceToken, form.DeviceToken) { + user.DeviceToken = append(user.DeviceToken, form.DeviceToken) + } + // Update last login err = s.repository.Login(ctx, user.ID.Hex()) if err != nil {