Enhance Notification System with New API Endpoints and Response Structures

- Added new endpoints for marking notifications as seen and retrieving detailed notification information for both admin and user contexts, improving the flexibility of the notification system.
- Implemented the MarkSeen and ViewNotification methods in the NotificationHandler, ensuring proper handling of notification visibility and details retrieval.
- Updated the NotificationService interface to include methods for getting notification details and marking notifications as seen, enhancing service capabilities.
- Enhanced the notification response structures to include additional fields such as Image, Seen, and ScheduledAt, providing richer data in API responses.
- Updated API documentation with Swagger comments for the new endpoints and response formats, ensuring clarity for API consumers.
This commit is contained in:
n.nakhostin
2025-09-21 11:26:54 +03:30
parent 0f981880b5
commit 5906904caf
12 changed files with 1200 additions and 45 deletions
+323 -9
View File
@@ -2619,6 +2619,55 @@ const docTemplate = `{
} }
} }
}, },
"/admin/v1/notifications/mark-seen/{id}": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "Mark a notification as seen",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Admin-Notification"
],
"summary": "Mark a notification as seen",
"parameters": [
{
"type": "string",
"description": "Notification ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/admin/v1/notifications/my": { "/admin/v1/notifications/my": {
"get": { "get": {
"security": [ "security": [
@@ -2739,6 +2788,73 @@ const docTemplate = `{
} }
} }
}, },
"/admin/v1/notifications/view/{id}": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "Get detailed information about a specific notification",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Admin-Notification"
],
"summary": "Get notification details",
"parameters": [
{
"type": "string",
"description": "Notification ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/internal_notification.NotificationResponse"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/admin/v1/profile": { "/admin/v1/profile": {
"get": { "get": {
"security": [ "security": [
@@ -5257,7 +5373,7 @@ const docTemplate = `{
} }
} }
}, },
"/api/v1/notifications/my": { "/api/v1/notifications": {
"get": { "get": {
"security": [ "security": [
{ {
@@ -5377,6 +5493,122 @@ const docTemplate = `{
} }
} }
}, },
"/api/v1/notifications/mark-seen/{id}": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "Mark a notification as seen",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Notification"
],
"summary": "Mark a notification as seen",
"parameters": [
{
"type": "string",
"description": "Notification ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/api/v1/notifications/view/{id}": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "Get detailed information about a specific notification",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Notification"
],
"summary": "Get notification details",
"parameters": [
{
"type": "string",
"description": "Notification ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/notification.DetailedNotificationResponse"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/api/v1/profile": { "/api/v1/profile": {
"get": { "get": {
"security": [ "security": [
@@ -7297,10 +7529,6 @@ const docTemplate = `{
"type": "string", "type": "string",
"example": "User" "example": "User"
}, },
"password": {
"type": "string",
"example": "User!1234"
},
"phone": { "phone": {
"type": "string", "type": "string",
"example": "+1234567890" "example": "+1234567890"
@@ -7627,9 +7855,6 @@ const docTemplate = `{
"type": "object", "type": "object",
"properties": { "properties": {
"created_at": { "created_at": {
"type": "integer"
},
"description": {
"type": "string" "type": "string"
}, },
"event_type": { "event_type": {
@@ -7638,9 +7863,18 @@ const docTemplate = `{
"id": { "id": {
"type": "string" "type": "string"
}, },
"image": {
"type": "string"
},
"is_scheduled": {
"type": "boolean"
},
"link": { "link": {
"type": "string" "type": "string"
}, },
"message": {
"type": "string"
},
"metadata": { "metadata": {
"type": "object", "type": "object",
"additionalProperties": {} "additionalProperties": {}
@@ -7657,6 +7891,15 @@ const docTemplate = `{
"schedule_at": { "schedule_at": {
"type": "integer" "type": "integer"
}, },
"scheduled_at": {
"type": "integer"
},
"seen": {
"type": "boolean"
},
"seen_at": {
"type": "integer"
},
"status": { "status": {
"type": "string" "type": "string"
}, },
@@ -7667,7 +7910,7 @@ const docTemplate = `{
"type": "string" "type": "string"
}, },
"updated_at": { "updated_at": {
"type": "integer" "type": "string"
}, },
"user_id": { "user_id": {
"type": "string" "type": "string"
@@ -7685,6 +7928,77 @@ const docTemplate = `{
"DeliveryChannelEmail" "DeliveryChannelEmail"
] ]
}, },
"notification.DetailedNotificationResponse": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"event_type": {
"type": "string"
},
"id": {
"type": "string"
},
"image": {
"type": "string"
},
"is_scheduled": {
"type": "boolean"
},
"link": {
"type": "string"
},
"message": {
"type": "string"
},
"metadata": {
"type": "object",
"additionalProperties": {}
},
"methods": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"priority": {
"description": "low, medium, important",
"type": "string"
},
"scheduled_at": {
"type": "integer"
},
"seen": {
"type": "boolean"
},
"seen_at": {
"type": "integer"
},
"status": {
"description": "pending, sent, failed",
"type": "string"
},
"title": {
"type": "string"
},
"type": {
"description": "info, warning, alert, reject",
"type": "string"
},
"updated_at": {
"type": "string"
},
"user_id": {
"type": "string"
}
}
}
}
},
"notification.NotificationPriority": { "notification.NotificationPriority": {
"type": "string", "type": "string",
"enum": [ "enum": [
+323 -9
View File
@@ -2613,6 +2613,55 @@
} }
} }
}, },
"/admin/v1/notifications/mark-seen/{id}": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "Mark a notification as seen",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Admin-Notification"
],
"summary": "Mark a notification as seen",
"parameters": [
{
"type": "string",
"description": "Notification ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/admin/v1/notifications/my": { "/admin/v1/notifications/my": {
"get": { "get": {
"security": [ "security": [
@@ -2733,6 +2782,73 @@
} }
} }
}, },
"/admin/v1/notifications/view/{id}": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "Get detailed information about a specific notification",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Admin-Notification"
],
"summary": "Get notification details",
"parameters": [
{
"type": "string",
"description": "Notification ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/internal_notification.NotificationResponse"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/admin/v1/profile": { "/admin/v1/profile": {
"get": { "get": {
"security": [ "security": [
@@ -5251,7 +5367,7 @@
} }
} }
}, },
"/api/v1/notifications/my": { "/api/v1/notifications": {
"get": { "get": {
"security": [ "security": [
{ {
@@ -5371,6 +5487,122 @@
} }
} }
}, },
"/api/v1/notifications/mark-seen/{id}": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "Mark a notification as seen",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Notification"
],
"summary": "Mark a notification as seen",
"parameters": [
{
"type": "string",
"description": "Notification ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/api/v1/notifications/view/{id}": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "Get detailed information about a specific notification",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"Notification"
],
"summary": "Get notification details",
"parameters": [
{
"type": "string",
"description": "Notification ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/response.APIResponse"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/notification.DetailedNotificationResponse"
}
}
}
]
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.APIResponse"
}
}
}
}
},
"/api/v1/profile": { "/api/v1/profile": {
"get": { "get": {
"security": [ "security": [
@@ -7291,10 +7523,6 @@
"type": "string", "type": "string",
"example": "User" "example": "User"
}, },
"password": {
"type": "string",
"example": "User!1234"
},
"phone": { "phone": {
"type": "string", "type": "string",
"example": "+1234567890" "example": "+1234567890"
@@ -7621,9 +7849,6 @@
"type": "object", "type": "object",
"properties": { "properties": {
"created_at": { "created_at": {
"type": "integer"
},
"description": {
"type": "string" "type": "string"
}, },
"event_type": { "event_type": {
@@ -7632,9 +7857,18 @@
"id": { "id": {
"type": "string" "type": "string"
}, },
"image": {
"type": "string"
},
"is_scheduled": {
"type": "boolean"
},
"link": { "link": {
"type": "string" "type": "string"
}, },
"message": {
"type": "string"
},
"metadata": { "metadata": {
"type": "object", "type": "object",
"additionalProperties": {} "additionalProperties": {}
@@ -7651,6 +7885,15 @@
"schedule_at": { "schedule_at": {
"type": "integer" "type": "integer"
}, },
"scheduled_at": {
"type": "integer"
},
"seen": {
"type": "boolean"
},
"seen_at": {
"type": "integer"
},
"status": { "status": {
"type": "string" "type": "string"
}, },
@@ -7661,7 +7904,7 @@
"type": "string" "type": "string"
}, },
"updated_at": { "updated_at": {
"type": "integer" "type": "string"
}, },
"user_id": { "user_id": {
"type": "string" "type": "string"
@@ -7679,6 +7922,77 @@
"DeliveryChannelEmail" "DeliveryChannelEmail"
] ]
}, },
"notification.DetailedNotificationResponse": {
"type": "object",
"properties": {
"data": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"event_type": {
"type": "string"
},
"id": {
"type": "string"
},
"image": {
"type": "string"
},
"is_scheduled": {
"type": "boolean"
},
"link": {
"type": "string"
},
"message": {
"type": "string"
},
"metadata": {
"type": "object",
"additionalProperties": {}
},
"methods": {
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"priority": {
"description": "low, medium, important",
"type": "string"
},
"scheduled_at": {
"type": "integer"
},
"seen": {
"type": "boolean"
},
"seen_at": {
"type": "integer"
},
"status": {
"description": "pending, sent, failed",
"type": "string"
},
"title": {
"type": "string"
},
"type": {
"description": "info, warning, alert, reject",
"type": "string"
},
"updated_at": {
"type": "string"
},
"user_id": {
"type": "string"
}
}
}
}
},
"notification.NotificationPriority": { "notification.NotificationPriority": {
"type": "string", "type": "string",
"enum": [ "enum": [
+204 -7
View File
@@ -469,9 +469,6 @@ definitions:
full_name: full_name:
example: User example: User
type: string type: string
password:
example: User!1234
type: string
phone: phone:
example: "+1234567890" example: "+1234567890"
type: string type: string
@@ -693,15 +690,19 @@ definitions:
internal_notification.NotificationResponse: internal_notification.NotificationResponse:
properties: properties:
created_at: created_at:
type: integer
description:
type: string type: string
event_type: event_type:
type: string type: string
id: id:
type: string type: string
image:
type: string
is_scheduled:
type: boolean
link: link:
type: string type: string
message:
type: string
metadata: metadata:
additionalProperties: {} additionalProperties: {}
type: object type: object
@@ -713,6 +714,12 @@ definitions:
type: string type: string
schedule_at: schedule_at:
type: integer type: integer
scheduled_at:
type: integer
seen:
type: boolean
seen_at:
type: integer
status: status:
type: string type: string
title: title:
@@ -720,7 +727,7 @@ definitions:
type: type:
type: string type: string
updated_at: updated_at:
type: integer type: string
user_id: user_id:
type: string type: string
type: object type: object
@@ -732,6 +739,54 @@ definitions:
x-enum-varnames: x-enum-varnames:
- DeliveryChannelPush - DeliveryChannelPush
- DeliveryChannelEmail - DeliveryChannelEmail
notification.DetailedNotificationResponse:
properties:
data:
properties:
created_at:
type: string
event_type:
type: string
id:
type: string
image:
type: string
is_scheduled:
type: boolean
link:
type: string
message:
type: string
metadata:
additionalProperties: {}
type: object
methods:
additionalProperties:
type: string
type: object
priority:
description: low, medium, important
type: string
scheduled_at:
type: integer
seen:
type: boolean
seen_at:
type: integer
status:
description: pending, sent, failed
type: string
title:
type: string
type:
description: info, warning, alert, reject
type: string
updated_at:
type: string
user_id:
type: string
type: object
type: object
notification.NotificationPriority: notification.NotificationPriority:
enum: enum:
- low - low
@@ -2874,6 +2929,37 @@ paths:
summary: Create a new notification summary: Create a new notification
tags: tags:
- Admin-Notification - Admin-Notification
/admin/v1/notifications/mark-seen/{id}:
get:
consumes:
- application/json
description: Mark a notification as seen
parameters:
- description: Notification ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.APIResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
security:
- BearerAuth: []
summary: Mark a notification as seen
tags:
- Admin-Notification
/admin/v1/notifications/my: /admin/v1/notifications/my:
get: get:
consumes: consumes:
@@ -2949,6 +3035,46 @@ paths:
summary: Get notifications for the admin summary: Get notifications for the admin
tags: tags:
- Admin-Notification - Admin-Notification
/admin/v1/notifications/view/{id}:
get:
consumes:
- application/json
description: Get detailed information about a specific notification
parameters:
- description: Notification ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.APIResponse'
- properties:
data:
$ref: '#/definitions/internal_notification.NotificationResponse'
type: object
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
security:
- BearerAuth: []
summary: Get notification details
tags:
- Admin-Notification
/admin/v1/profile: /admin/v1/profile:
get: get:
consumes: consumes:
@@ -4518,7 +4644,7 @@ paths:
summary: Create new inquiry summary: Create new inquiry
tags: tags:
- Inquiries - Inquiries
/api/v1/notifications/my: /api/v1/notifications:
get: get:
consumes: consumes:
- application/json - application/json
@@ -4593,6 +4719,77 @@ paths:
summary: Get notifications for the user summary: Get notifications for the user
tags: tags:
- Notification - Notification
/api/v1/notifications/mark-seen/{id}:
get:
consumes:
- application/json
description: Mark a notification as seen
parameters:
- description: Notification ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.APIResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
security:
- BearerAuth: []
summary: Mark a notification as seen
tags:
- Notification
/api/v1/notifications/view/{id}:
get:
consumes:
- application/json
description: Get detailed information about a specific notification
parameters:
- description: Notification ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/response.APIResponse'
- properties:
data:
$ref: '#/definitions/notification.DetailedNotificationResponse'
type: object
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.APIResponse'
"404":
description: Not Found
schema:
$ref: '#/definitions/response.APIResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.APIResponse'
security:
- BearerAuth: []
summary: Get notification details
tags:
- Notification
/api/v1/profile: /api/v1/profile:
get: get:
consumes: consumes:
+4
View File
@@ -137,6 +137,8 @@ func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler
notificationsGP.Use(userHandler.AuthMiddleware()) notificationsGP.Use(userHandler.AuthMiddleware())
notificationsGP.POST("", notificationHandler.Send) notificationsGP.POST("", notificationHandler.Send)
notificationsGP.GET("", notificationHandler.GetNotifications) notificationsGP.GET("", notificationHandler.GetNotifications)
notificationsGP.GET("/view/:id", notificationHandler.ViewNotification)
notificationsGP.GET("/mark-seen/:id", notificationHandler.MarkSeen)
notificationsGP.GET("/my", notificationHandler.AdminNotifications) notificationsGP.GET("/my", notificationHandler.AdminNotifications)
} }
} }
@@ -215,5 +217,7 @@ func RegisterPublicRoutes(e *echo.Echo, customerHandler *customer.Handler, tende
{ {
notificationsGP.Use(customerHandler.AuthMiddleware()) notificationsGP.Use(customerHandler.AuthMiddleware())
notificationsGP.GET("", notificationHandler.CustomerNotifications) notificationsGP.GET("", notificationHandler.CustomerNotifications)
notificationsGP.GET("/view/:id", notificationHandler.PublicViewNotification)
notificationsGP.GET("mark-seen/:id", notificationHandler.PublicMarkSeen)
} }
} }
+12 -4
View File
@@ -1,6 +1,9 @@
package notification package notification
import "tm/pkg/response" import (
"time"
"tm/pkg/response"
)
// CreateRequest represents the request to create a notification // CreateRequest represents the request to create a notification
type NotificationRequest struct { type NotificationRequest struct {
@@ -34,8 +37,9 @@ type NotificationResponse struct {
ID string `json:"id"` ID string `json:"id"`
UserID string `json:"user_id"` UserID string `json:"user_id"`
Title string `json:"title"` Title string `json:"title"`
Description string `json:"description"` Message string `json:"message"`
Link *string `json:"link"` Link *string `json:"link"`
Image *string `json:"image"`
Priority string `json:"priority"` Priority string `json:"priority"`
Type string `json:"type"` Type string `json:"type"`
EventType string `json:"event_type"` EventType string `json:"event_type"`
@@ -43,6 +47,10 @@ type NotificationResponse struct {
Methods map[string]string `json:"methods"` Methods map[string]string `json:"methods"`
Metadata map[string]any `json:"metadata"` Metadata map[string]any `json:"metadata"`
ScheduleAt int64 `json:"schedule_at"` ScheduleAt int64 `json:"schedule_at"`
CreatedAt int64 `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt int64 `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
Seen bool `json:"seen"`
SeenAt int64 `json:"seen_at"`
IsScheduled bool `json:"is_scheduled"`
ScheduledAt int64 `json:"scheduled_at"`
} }
+118 -2
View File
@@ -47,7 +47,7 @@ func (h *NotificationHandler) Send(c echo.Context) error {
} }
// Send notification // Send notification
go h.service.Send(c.Request().Context(), &req) h.service.Send(c.Request().Context(), &req)
return response.Created(c, nil, "Notification sent successfully") return response.Created(c, nil, "Notification sent successfully")
} }
@@ -148,6 +148,65 @@ func (h *NotificationHandler) AdminNotifications(c echo.Context) error {
return response.Success(c, notifications, "Notifications retrieved successfully") return response.Success(c, notifications, "Notifications retrieved successfully")
} }
// MarkSeen marks a notification as seen
// @Summary Mark a notification as seen
// @Description Mark a notification as seen
// @Tags Admin-Notification
// @Accept json
// @Produce json
// @Param id path string true "Notification ID"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Security BearerAuth
// @Router /admin/v1/notifications/mark-seen/{id} [get]
func (h *NotificationHandler) MarkSeen(c echo.Context) error {
notificationID := c.Param("id")
userID, err := user.GetUserIDFromContext(c)
if err != nil {
return response.BadRequest(c, "User ID required", "User must be associated with a user")
}
// Mark notification as seen
err = h.service.MarkSeen(c.Request().Context(), notificationID, userID)
if err != nil {
return response.InternalServerError(c, "Failed to mark notification as seen")
}
return response.Success(c, nil, "Notification marked as seen successfully")
}
// ViewNotification gets a single notification by ID
// @Summary Get notification details
// @Description Get detailed information about a specific notification
// @Tags Admin-Notification
// @Accept json
// @Produce json
// @Param id path string true "Notification ID"
// @Success 200 {object} response.APIResponse{data=NotificationResponse}
// @Failure 400 {object} response.APIResponse
// @Failure 404 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Security BearerAuth
// @Router /admin/v1/notifications/view/{id} [get]
func (h *NotificationHandler) ViewNotification(c echo.Context) error {
notificationID := c.Param("id")
if notificationID == "" {
return response.BadRequest(c, "notification ID is required", "Notification ID must be provided")
}
// Get notification details
notification, err := h.service.GetNotification(c.Request().Context(), notificationID)
if err != nil {
return response.InternalServerError(c, "Failed to get notification details")
}
return response.Success(c, notification, "Notification details retrieved successfully")
}
// **************************** PUBLIC ENDPOINTS ****************************
// MyNotifications gets notifications for the user // MyNotifications gets notifications for the user
// @Summary Get notifications for the user // @Summary Get notifications for the user
// @Description Get notifications for the user // @Description Get notifications for the user
@@ -169,7 +228,7 @@ func (h *NotificationHandler) AdminNotifications(c echo.Context) error {
// @Failure 400 {object} response.APIResponse // @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse // @Failure 500 {object} response.APIResponse
// @Security BearerAuth // @Security BearerAuth
// @Router /api/v1/notifications/my [get] // @Router /api/v1/notifications [get]
func (h *NotificationHandler) CustomerNotifications(c echo.Context) error { func (h *NotificationHandler) CustomerNotifications(c echo.Context) error {
userID, err := user.GetUserIDFromContext(c) userID, err := user.GetUserIDFromContext(c)
if err != nil { if err != nil {
@@ -199,3 +258,60 @@ func (h *NotificationHandler) CustomerNotifications(c echo.Context) error {
return response.Success(c, notifications, "Notifications retrieved successfully") return response.Success(c, notifications, "Notifications retrieved successfully")
} }
// MarkSeen marks a notification as seen
// @Summary Mark a notification as seen
// @Description Mark a notification as seen
// @Tags Notification
// @Accept json
// @Produce json
// @Param id path string true "Notification ID"
// @Success 200 {object} response.APIResponse
// @Failure 400 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Security BearerAuth
// @Router /api/v1/notifications/mark-seen/{id} [get]
func (h *NotificationHandler) PublicMarkSeen(c echo.Context) error {
notificationID := c.Param("id")
userID, err := user.GetUserIDFromContext(c)
if err != nil {
return response.BadRequest(c, "User ID required", "User must be associated with a user")
}
// Mark notification as seen
err = h.service.MarkSeen(c.Request().Context(), notificationID, userID)
if err != nil {
return response.InternalServerError(c, "Failed to mark notification as seen")
}
return response.Success(c, nil, "Notification marked as seen successfully")
}
// ViewNotification gets a single notification by ID
// @Summary Get notification details
// @Description Get detailed information about a specific notification
// @Tags Notification
// @Accept json
// @Produce json
// @Param id path string true "Notification ID"
// @Success 200 {object} response.APIResponse{data=DetailedNotificationResponse}
// @Failure 400 {object} response.APIResponse
// @Failure 404 {object} response.APIResponse
// @Failure 500 {object} response.APIResponse
// @Security BearerAuth
// @Router /api/v1/notifications/view/{id} [get]
func (h *NotificationHandler) PublicViewNotification(c echo.Context) error {
notificationID := c.Param("id")
if notificationID == "" {
return response.BadRequest(c, "notification ID is required", "Notification ID must be provided")
}
// Get notification details
notification, err := h.service.GetNotification(c.Request().Context(), notificationID)
if err != nil {
return response.InternalServerError(c, "Failed to get notification details")
}
return response.Success(c, notification, "Notification details retrieved successfully")
}
+102 -3
View File
@@ -3,6 +3,8 @@ package notification
import ( import (
"context" "context"
"errors" "errors"
"strings"
"time"
"tm/internal/customer" "tm/internal/customer"
"tm/internal/user" "tm/internal/user"
@@ -15,6 +17,8 @@ import (
type Service interface { type Service interface {
Send(ctx context.Context, req *NotificationRequest) error Send(ctx context.Context, req *NotificationRequest) error
GetNotifications(ctx context.Context, req *SearchForm, pagination *response.Pagination) (*NotificationListResponse, error) GetNotifications(ctx context.Context, req *SearchForm, pagination *response.Pagination) (*NotificationListResponse, error)
GetNotification(ctx context.Context, notificationID string) (*NotificationResponse, error)
MarkSeen(ctx context.Context, notificationID, userID string) error
getUsers(ctx context.Context, userIDs []string) ([]notificationRecipient, error) getUsers(ctx context.Context, userIDs []string) ([]notificationRecipient, error)
getCustomers(ctx context.Context, values []string, target string) ([]notificationRecipient, error) getCustomers(ctx context.Context, values []string, target string) ([]notificationRecipient, error)
} }
@@ -65,7 +69,7 @@ func (s *notificationService) Send(ctx context.Context, req *NotificationRequest
Priority: string(req.Priority), Priority: string(req.Priority),
EventType: notification.EventTypePush, EventType: notification.EventTypePush,
Metadata: map[string]any{ Metadata: map[string]any{
"tender": req.Tender, "tender": strings.TrimSpace(req.Tender),
}, },
Methods: notification.NotificationMethods{ Methods: notification.NotificationMethods{
Push: v, Push: v,
@@ -84,7 +88,7 @@ func (s *notificationService) Send(ctx context.Context, req *NotificationRequest
Priority: string(req.Priority), Priority: string(req.Priority),
EventType: notification.EventTypeEmail, EventType: notification.EventTypeEmail,
Metadata: map[string]any{ Metadata: map[string]any{
"tender": req.Tender, "tender": strings.TrimSpace(req.Tender),
}, },
Methods: notification.NotificationMethods{ Methods: notification.NotificationMethods{
Email: recipient.Email, Email: recipient.Email,
@@ -111,6 +115,9 @@ func (s *notificationService) Send(ctx context.Context, req *NotificationRequest
Push: v, Push: v,
}, },
ScheduledAt: req.ScheduleAt, ScheduledAt: req.ScheduleAt,
Metadata: map[string]any{
"tender": strings.TrimSpace(req.Tender),
},
}) })
} }
@@ -126,6 +133,9 @@ func (s *notificationService) Send(ctx context.Context, req *NotificationRequest
Email: recipient.Email, Email: recipient.Email,
}, },
ScheduledAt: req.ScheduleAt, ScheduledAt: req.ScheduleAt,
Metadata: map[string]any{
"tender": strings.TrimSpace(req.Tender),
},
}) })
} }
} }
@@ -168,11 +178,19 @@ func (s *notificationService) GetNotifications(ctx context.Context, req *SearchF
notificationsResponse := make([]*NotificationResponse, 0) notificationsResponse := make([]*NotificationResponse, 0)
for _, notification := range notifications.Data { for _, notification := range notifications.Data {
createdAt, err := time.Parse(time.RFC3339, notification.CreatedAt)
if err != nil {
return nil, err
}
updatedAt, err := time.Parse(time.RFC3339, notification.UpdatedAt)
if err != nil {
return nil, err
}
notificationsResponse = append(notificationsResponse, &NotificationResponse{ notificationsResponse = append(notificationsResponse, &NotificationResponse{
ID: notification.ID, ID: notification.ID,
UserID: notification.UserID, UserID: notification.UserID,
Title: notification.Title, Title: notification.Title,
Description: notification.Message, Message: notification.Message,
Link: &notification.Link, Link: &notification.Link,
Priority: notification.Priority, Priority: notification.Priority,
Type: notification.Type, Type: notification.Type,
@@ -181,6 +199,13 @@ func (s *notificationService) GetNotifications(ctx context.Context, req *SearchF
Methods: notification.Methods, Methods: notification.Methods,
Metadata: notification.Metadata, Metadata: notification.Metadata,
ScheduleAt: notification.ScheduledAt, ScheduleAt: notification.ScheduledAt,
IsScheduled: notification.IsScheduled,
Image: &notification.Image,
Seen: notification.Seen,
SeenAt: notification.SeenAt,
ScheduledAt: notification.ScheduledAt,
CreatedAt: createdAt,
UpdatedAt: updatedAt,
}) })
} }
@@ -196,6 +221,80 @@ func (s *notificationService) GetNotifications(ctx context.Context, req *SearchF
}, nil }, nil
} }
func (s *notificationService) GetNotification(ctx context.Context, notificationID string) (*NotificationResponse, error) {
s.logger.Info("Getting notification", map[string]interface{}{
"notification_id": notificationID,
})
notification, err := s.sdk.GetNotification(ctx, notificationID)
if err != nil {
s.logger.Error("Failed to get notification", map[string]interface{}{
"error": err.Error(),
"notification_id": notificationID,
})
return nil, err
}
// Convert to detailed response format
var linkPtr *string
if notification.Data.Link != "" {
linkPtr = &notification.Data.Link
}
var imagePtr *string
if notification.Data.Image != "" {
imagePtr = &notification.Data.Image
}
detailedResponse := &NotificationResponse{
ID: notification.Data.ID,
UserID: notification.Data.UserID,
Title: notification.Data.Title,
Message: notification.Data.Message,
Link: linkPtr,
Image: imagePtr,
Type: notification.Data.Type,
Priority: notification.Data.Priority,
EventType: notification.Data.EventType,
CreatedAt: notification.Data.CreatedAt,
UpdatedAt: notification.Data.UpdatedAt,
Methods: notification.Data.Methods,
Metadata: notification.Data.Metadata,
Status: notification.Data.Status,
Seen: notification.Data.Seen,
SeenAt: notification.Data.SeenAt,
ScheduledAt: notification.Data.ScheduledAt,
IsScheduled: notification.Data.IsScheduled,
}
return detailedResponse, nil
}
func (s *notificationService) MarkSeen(ctx context.Context, notificationID, userID string) error {
s.logger.Info("Marking notification as seen", map[string]interface{}{
"notification_id": notificationID,
})
notif, err := s.sdk.GetNotification(ctx, notificationID)
if err != nil {
s.logger.Error("Failed to get notification", map[string]interface{}{
"error": err.Error(),
"notification_id": notificationID,
})
return err
}
if notif.Data.UserID != userID {
return errors.New("notification not found")
}
_, err = s.sdk.MarkSeen(ctx, notificationID)
if err != nil {
return err
}
return nil
}
func (s *notificationService) getUsers(ctx context.Context, userIDs []string) ([]notificationRecipient, error) { func (s *notificationService) getUsers(ctx context.Context, userIDs []string) ([]notificationRecipient, error) {
recipients := make([]notificationRecipient, 0) recipients := make([]notificationRecipient, 0)
offset := 0 offset := 0
+67 -3
View File
@@ -71,9 +71,9 @@ func NewClientWithHTTPClient(config *Config, httpClient HTTPClient, logger Logge
// SendNotification sends a notification request to the notification service // SendNotification sends a notification request to the notification service
func (c *Client) SendNotification(ctx context.Context, req *NotificationRequest) (*NotificationResponse, error) { func (c *Client) SendNotification(ctx context.Context, req *NotificationRequest) (*NotificationResponse, error) {
// Validate request // Validate request
if err := req.Validate(); err != nil { // if err := req.Validate(); err != nil {
return nil, fmt.Errorf("request validation failed: %w", err) // return nil, fmt.Errorf("request validation failed: %w", err)
} // }
var lastErr error var lastErr error
start := time.Now() start := time.Now()
@@ -265,6 +265,70 @@ func (c *Client) GetNotifications(ctx context.Context, req *GetNotificationsRequ
return &notificationsResp, nil return &notificationsResp, nil
} }
// GetNotification retrieves a single notification by ID from the notification service
func (c *Client) GetNotification(ctx context.Context, notificationID string) (*DetailedNotificationResponse, error) {
if notificationID == "" {
return nil, fmt.Errorf("notification ID is required")
}
if c.config.EnableLogging && c.logger != nil {
c.logger.Debug("Getting notification", map[string]interface{}{
"notification_id": notificationID,
"base_url": c.config.BaseURL,
})
}
// Create HTTP request
url := fmt.Sprintf("%s/api/v1/notifications/view/%s", c.config.BaseURL, notificationID)
httpReq, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
}
// Set headers
httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("User-Agent", c.config.UserAgent)
// Send request
resp, err := c.httpClient.Do(httpReq)
if err != nil {
return nil, fmt.Errorf("HTTP request failed: %w", err)
}
defer resp.Body.Close()
// Read response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("failed to read response body: %w", err)
}
// Handle non-2xx status codes
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
// Try to parse error response
var errorResp NotificationErrorResponse
if json.Unmarshal(body, &errorResp) == nil && errorResp.Error != "" {
return nil, MapHTTPError(resp.StatusCode, errorResp.Error)
}
return nil, MapHTTPError(resp.StatusCode, string(body))
}
// Parse success response
var notificationResp DetailedNotificationResponse
if err := json.Unmarshal(body, &notificationResp); err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
if c.config.EnableLogging && c.logger != nil {
c.logger.Info("Notification retrieved successfully", map[string]interface{}{
"notification_id": notificationResp.Data.ID,
"user_id": notificationResp.Data.UserID,
"type": notificationResp.Data.Type,
})
}
return &notificationResp, nil
}
// buildQueryParams builds query parameters from the request // buildQueryParams builds query parameters from the request
func (c *Client) buildQueryParams(req *GetNotificationsRequest) string { func (c *Client) buildQueryParams(req *GetNotificationsRequest) string {
params := make([]string, 0) params := make([]string, 0)
+34 -8
View File
@@ -1,6 +1,8 @@
package notification package notification
import ( import (
"time"
"github.com/asaskevich/govalidator" "github.com/asaskevich/govalidator"
) )
@@ -24,15 +26,15 @@ type NotificationMethods struct {
// NotificationRequest represents the request structure for sending notifications // NotificationRequest represents the request structure for sending notifications
type NotificationRequest struct { type NotificationRequest struct {
UserID string `json:"user_id,omitempty" valid:"optional"` UserID string `json:"user_id"`
Title string `json:"title" valid:"required"` Title string `json:"title"`
Message string `json:"message" valid:"required,length(1|1000)"` Message string `json:"message"`
Type string `json:"type" valid:"required,in(info|warning|alert|reject)"` Type string `json:"type"`
EventType EventType `json:"event_type" valid:"required,in(EMAIL|SMS|PUSH|OTP)"` EventType EventType `json:"event_type"`
Priority string `json:"priority" valid:"required,in(low|medium|important)"` Priority string `json:"priority"`
ScheduledAt int64 `json:"scheduled_at"` ScheduledAt int64 `json:"scheduled_at"`
Metadata map[string]any `json:"metadata,omitempty" valid:"optional"` Metadata map[string]any `json:"metadata"`
Methods NotificationMethods `json:"methods" valid:"required"` Methods NotificationMethods `json:"methods"`
} }
// NotificationResponse represents the successful response from notification service // NotificationResponse represents the successful response from notification service
@@ -40,6 +42,30 @@ type NotificationResponse struct {
Status string `json:"status"` Status string `json:"status"`
} }
// DetailedNotificationResponse represents a detailed notification response
type DetailedNotificationResponse struct {
Data struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Title string `json:"title"`
Message string `json:"message"`
Link string `json:"link"`
Image string `json:"image"`
Type string `json:"type"` // info, warning, alert, reject
Priority string `json:"priority"` // low, medium, important
EventType string `json:"event_type"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Methods map[string]string `json:"methods"`
Metadata map[string]any `json:"metadata"`
Status string `json:"status"` // pending, sent, failed
Seen bool `json:"seen"`
SeenAt int64 `json:"seen_at"`
ScheduledAt int64 `json:"scheduled_at"`
IsScheduled bool `json:"is_scheduled"`
} `json:"data"`
}
// NotificationErrorResponse represents the error response from notification service // NotificationErrorResponse represents the error response from notification service
type NotificationErrorResponse struct { type NotificationErrorResponse struct {
Error string `json:"error"` Error string `json:"error"`
+3
View File
@@ -24,6 +24,9 @@ type NotificationService interface {
// GetNotifications retrieves a list of notifications // GetNotifications retrieves a list of notifications
GetNotifications(ctx context.Context, req *GetNotificationsRequest) (*NotificationListResponse, error) GetNotifications(ctx context.Context, req *GetNotificationsRequest) (*NotificationListResponse, error)
// GetNotification retrieves a single notification by ID
GetNotification(ctx context.Context, notificationID string) (*DetailedNotificationResponse, error)
// MarkSeen marks a notification as seen // MarkSeen marks a notification as seen
MarkSeen(ctx context.Context, notificationID string) (*MarkSeenResponse, error) MarkSeen(ctx context.Context, notificationID string) (*MarkSeenResponse, error)
+5
View File
@@ -79,6 +79,11 @@ func (s *SDK) GetNotifications(ctx context.Context, req *GetNotificationsRequest
return s.service.GetNotifications(ctx, req) return s.service.GetNotifications(ctx, req)
} }
// GetNotification retrieves a single notification by ID
func (s *SDK) GetNotification(ctx context.Context, notificationID string) (*DetailedNotificationResponse, error) {
return s.service.GetNotification(ctx, notificationID)
}
// MarkSeen marks a notification as seen // MarkSeen marks a notification as seen
func (s *SDK) MarkSeen(ctx context.Context, notificationID string) (*MarkSeenResponse, error) { func (s *SDK) MarkSeen(ctx context.Context, notificationID string) (*MarkSeenResponse, error) {
return s.service.MarkSeen(ctx, notificationID) return s.service.MarkSeen(ctx, notificationID)
+5
View File
@@ -109,6 +109,11 @@ func (s *Service) GetNotifications(ctx context.Context, req *GetNotificationsReq
return s.client.GetNotifications(ctx, req) return s.client.GetNotifications(ctx, req)
} }
// GetNotification retrieves a single notification by ID
func (s *Service) GetNotification(ctx context.Context, notificationID string) (*DetailedNotificationResponse, error) {
return s.client.GetNotification(ctx, notificationID)
}
// MarkSeen marks a notification as seen // MarkSeen marks a notification as seen
func (s *Service) MarkSeen(ctx context.Context, notificationID string) (*MarkSeenResponse, error) { func (s *Service) MarkSeen(ctx context.Context, notificationID string) (*MarkSeenResponse, error) {
return s.client.MarkSeen(ctx, notificationID) return s.client.MarkSeen(ctx, notificationID)