feat: update ai-chat an chat_history
This commit is contained in:
parent
a88bd957e3
commit
f1b49d0c63
|
|
@ -30,4 +30,4 @@ deploy:
|
|||
services:
|
||||
- docker:dind
|
||||
script:
|
||||
- curl --user $JENKINS_USER:$JENKINS_PWD http://38.47.180.165:8080/job/autodeploy-medols-be/build?token=autodeploymedols
|
||||
- curl --user $JENKINS_USER:$JENKINS_PWD http://38.47.180.165:8080/job/autodeploy-narasiahli-be/build?token=autodeploynarasiahli
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
# Chat History Implementation
|
||||
|
||||
This document describes the implementation of the chat history functionality based on the old web API code from the `plan/old` folder.
|
||||
|
||||
## Overview
|
||||
|
||||
The implementation includes:
|
||||
1. **New Chat History Module**: A complete module for managing chat sessions and messages
|
||||
2. **Updated AI Chat Module**: Enhanced with proper routing
|
||||
3. **Database Entities**: New entities for chat sessions and messages
|
||||
4. **API Endpoints**: RESTful endpoints for all chat history operations
|
||||
|
||||
## New Files Created
|
||||
|
||||
### Database Entities
|
||||
- `app/database/entity/chat_sessions.entity.go` - Chat sessions entity
|
||||
- `app/database/entity/chat_messages_new.entity.go` - Chat messages entity (renamed to avoid conflicts)
|
||||
|
||||
### Chat History Module
|
||||
- `app/module/chat_history/chat_history.module.go` - Module definition
|
||||
- `app/module/chat_history/chat_history.router.go` - Router configuration
|
||||
- `app/module/chat_history/request/chat_history.request.go` - Request DTOs
|
||||
- `app/module/chat_history/response/chat_history.response.go` - Response DTOs
|
||||
- `app/module/chat_history/mapper/chat_history.mapper.go` - Entity to response mappers
|
||||
- `app/module/chat_history/repository/chat_history.repository.go` - Data access layer
|
||||
- `app/module/chat_history/service/chat_history.service.go` - Business logic layer
|
||||
- `app/module/chat_history/controller/chat_history.controller.go` - API controller
|
||||
|
||||
### AI Chat Module Updates
|
||||
- `app/module/ai_chat/ai_chat.router.go` - Router configuration for AI chat
|
||||
|
||||
### Database Migration
|
||||
- `migrations/001_create_chat_history_tables.sql` - SQL migration script
|
||||
- `migrations/001_create_chat_history_tables.go` - Go migration script
|
||||
- `scripts/migrate.go` - Migration runner script
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Chat History Endpoints
|
||||
|
||||
#### Sessions
|
||||
- `GET /chat-history/sessions` - Get user's chat sessions
|
||||
- `GET /chat-history/sessions/{sessionId}` - Get specific session with messages
|
||||
- `POST /chat-history/sessions` - Create new session
|
||||
- `PUT /chat-history/sessions/{sessionId}` - Update session
|
||||
- `DELETE /chat-history/sessions/{sessionId}` - Delete session
|
||||
|
||||
#### Messages
|
||||
- `GET /chat-history/sessions/{sessionId}/messages` - Get session messages
|
||||
- `POST /chat-history/sessions/{sessionId}/messages` - Create message
|
||||
- `PUT /chat-history/messages/{messageId}` - Update message
|
||||
- `DELETE /chat-history/messages/{messageId}` - Delete message
|
||||
|
||||
#### Combined Operations
|
||||
- `POST /chat-history/save` - Save complete chat history (sessions + messages)
|
||||
|
||||
### AI Chat Endpoints (Updated)
|
||||
- `GET /ai-chat/sessions` - Get user's AI chat sessions
|
||||
- `GET /ai-chat/sessions/{id}` - Get specific AI chat session
|
||||
- `POST /ai-chat/sessions` - Create AI chat session
|
||||
- `PUT /ai-chat/sessions/{id}` - Update AI chat session
|
||||
- `DELETE /ai-chat/sessions/{id}` - Delete AI chat session
|
||||
- `GET /ai-chat/sessions/{sessionId}/messages` - Get AI chat messages
|
||||
- `POST /ai-chat/sessions/{sessionId}/messages` - Send AI chat message
|
||||
- `PUT /ai-chat/sessions/{sessionId}/messages/{messageId}` - Update AI chat message
|
||||
- `DELETE /ai-chat/sessions/{sessionId}/messages/{messageId}` - Delete AI chat message
|
||||
- `GET /ai-chat/logs` - Get user's AI chat logs
|
||||
- `GET /ai-chat/logs/{id}` - Get specific AI chat log
|
||||
|
||||
## Database Schema
|
||||
|
||||
### chat_sessions Table
|
||||
```sql
|
||||
CREATE TABLE chat_sessions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
session_id VARCHAR(255) NOT NULL UNIQUE,
|
||||
user_id INTEGER NOT NULL,
|
||||
agent_id VARCHAR(255) NOT NULL,
|
||||
title VARCHAR(255) NOT NULL DEFAULT 'Chat Session',
|
||||
message_count INTEGER DEFAULT 0,
|
||||
status VARCHAR(50) DEFAULT 'active',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
### chat_messages_new Table
|
||||
```sql
|
||||
CREATE TABLE chat_messages_new (
|
||||
id SERIAL PRIMARY KEY,
|
||||
session_id VARCHAR(255) NOT NULL,
|
||||
message_type VARCHAR(50) NOT NULL CHECK (message_type IN ('user', 'assistant')),
|
||||
content TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
## Key Features Implemented
|
||||
|
||||
### 1. Session Management
|
||||
- Create, read, update, delete chat sessions
|
||||
- Session-based architecture with unique session IDs
|
||||
- User ownership validation
|
||||
- Message count tracking
|
||||
|
||||
### 2. Message Management
|
||||
- Create, read, update, delete messages within sessions
|
||||
- Support for user and assistant message types
|
||||
- Automatic message count updates
|
||||
- Cascade deletion when sessions are deleted
|
||||
|
||||
### 3. Combined Operations
|
||||
- Save complete chat history in one operation
|
||||
- Update existing sessions or create new ones
|
||||
- Bulk message operations
|
||||
|
||||
### 4. Security
|
||||
- User authentication required for all operations
|
||||
- User ownership validation for all resources
|
||||
- Proper error handling and validation
|
||||
|
||||
## Migration Instructions
|
||||
|
||||
### 1. Run Database Migration
|
||||
```bash
|
||||
# Option 1: Run the Go migration script
|
||||
go run scripts/migrate.go
|
||||
|
||||
# Option 2: Run SQL migration manually
|
||||
psql -d your_database -f migrations/001_create_chat_history_tables.sql
|
||||
```
|
||||
|
||||
### 2. Update Application
|
||||
The application has been updated to include:
|
||||
- New modules in `main.go`
|
||||
- Updated router configuration
|
||||
- New API endpoints
|
||||
|
||||
### 3. Test the Implementation
|
||||
```bash
|
||||
# Start the application
|
||||
go run main.go
|
||||
|
||||
# Test endpoints
|
||||
curl -X GET "http://localhost:8080/chat-history/sessions" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
|
||||
curl -X POST "http://localhost:8080/chat-history/save" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"user_id": 1,
|
||||
"agent_id": "agent-123",
|
||||
"session_id": "session-456",
|
||||
"title": "Test Chat",
|
||||
"messages": [
|
||||
{"type": "user", "content": "Hello"},
|
||||
{"type": "assistant", "content": "Hi there!"}
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
## Differences from Old Code
|
||||
|
||||
### Architecture Changes
|
||||
- **Language**: Converted from TypeScript/JavaScript to Go
|
||||
- **Framework**: Converted from Next.js API routes to Fiber (Go web framework)
|
||||
- **Database**: Converted from SQLite to PostgreSQL (via GORM)
|
||||
- **Structure**: Implemented clean architecture with repository, service, and controller layers
|
||||
|
||||
### API Changes
|
||||
- **Authentication**: Added Bearer token authentication
|
||||
- **Validation**: Added comprehensive request validation
|
||||
- **Error Handling**: Standardized error responses
|
||||
- **Pagination**: Added pagination support for list endpoints
|
||||
|
||||
### Database Changes
|
||||
- **Schema**: Adapted SQLite schema to PostgreSQL
|
||||
- **Relationships**: Added proper foreign key relationships
|
||||
- **Indexes**: Added performance indexes
|
||||
- **Constraints**: Added data validation constraints
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
1. **Real-time Features**: Add WebSocket support for real-time chat
|
||||
2. **File Attachments**: Support for file uploads in messages
|
||||
3. **Message Search**: Full-text search across messages
|
||||
4. **Analytics**: Chat analytics and reporting
|
||||
5. **Export**: Export chat history to various formats
|
||||
6. **Archiving**: Automatic archiving of old sessions
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
1. **Migration Errors**: Ensure database connection is properly configured
|
||||
2. **Authentication Errors**: Verify Bearer token is valid and user exists
|
||||
3. **Validation Errors**: Check request payload matches expected schema
|
||||
4. **Permission Errors**: Ensure user owns the resource being accessed
|
||||
|
||||
### Debug Mode
|
||||
Enable debug logging by setting the log level to debug in your configuration.
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions regarding this implementation, please refer to the application logs or contact the development team.
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ChatMessagesNew struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
SessionID string `json:"session_id" gorm:"type:varchar;not null;index"`
|
||||
MessageType string `json:"message_type" gorm:"type:varchar;not null;check:message_type IN ('user', 'assistant')"`
|
||||
Content string `json:"content" gorm:"type:text;not null"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
Session *ChatSessions `json:"session" gorm:"foreignKey:SessionID;references:SessionID"`
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"narasi-ahli-be/app/database/entity/users"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ChatSessions struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
SessionID string `json:"session_id" gorm:"type:varchar;not null;unique;index"`
|
||||
UserID uint `json:"user_id" gorm:"type:int4;not null;index"`
|
||||
AgentID string `json:"agent_id" gorm:"type:varchar;not null"`
|
||||
Title string `json:"title" gorm:"type:varchar;not null"`
|
||||
MessageCount int `json:"message_count" gorm:"type:int4;default:0"`
|
||||
Status string `json:"status" gorm:"type:varchar;default:'active'"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
User *users.Users `json:"user" gorm:"foreignKey:UserID;references:ID"`
|
||||
Messages []ChatMessages `json:"messages" gorm:"foreignKey:SessionID;references:SessionID"`
|
||||
}
|
||||
|
|
@ -4,24 +4,62 @@ import (
|
|||
"narasi-ahli-be/app/module/ai_chat/controller"
|
||||
"narasi-ahli-be/app/module/ai_chat/repository"
|
||||
"narasi-ahli-be/app/module/ai_chat/service"
|
||||
usersRepository "narasi-ahli-be/app/module/users/repository"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
var Module = fx.Options(
|
||||
fx.Provide(
|
||||
repository.NewAIChatRepository,
|
||||
service.NewAIChatService,
|
||||
controller.NewAIChatController,
|
||||
),
|
||||
fx.Invoke(func(
|
||||
aiChatController controller.AIChatController,
|
||||
usersRepo usersRepository.UsersRepository,
|
||||
log zerolog.Logger,
|
||||
) {
|
||||
log.Info().Msg("AI Chat module initialized successfully")
|
||||
}),
|
||||
// struct of AIChatRouter
|
||||
type AIChatRouter struct {
|
||||
App fiber.Router
|
||||
Controller controller.AIChatController
|
||||
}
|
||||
|
||||
// register bulky of AI Chat module
|
||||
var NewAIChatModule = fx.Options(
|
||||
// register repository of AI Chat module
|
||||
fx.Provide(repository.NewAIChatRepository),
|
||||
|
||||
// register service of AI Chat module
|
||||
fx.Provide(service.NewAIChatService),
|
||||
|
||||
// register controller of AI Chat module
|
||||
fx.Provide(controller.NewAIChatController),
|
||||
|
||||
// register router of AI Chat module
|
||||
fx.Provide(NewAIChatRouter),
|
||||
)
|
||||
|
||||
// init AIChatRouter
|
||||
func NewAIChatRouter(fiber *fiber.App, controller controller.AIChatController) *AIChatRouter {
|
||||
return &AIChatRouter{
|
||||
App: fiber,
|
||||
Controller: controller,
|
||||
}
|
||||
}
|
||||
|
||||
// register routes of AI Chat module
|
||||
func (_i *AIChatRouter) RegisterAIChatRoutes() {
|
||||
// define controllers
|
||||
aiChatController := _i.Controller
|
||||
|
||||
// define routes
|
||||
_i.App.Route("/ai-chat", func(router fiber.Router) {
|
||||
// Sessions routes
|
||||
router.Get("/sessions", aiChatController.GetUserSessions)
|
||||
router.Get("/sessions/:id", aiChatController.GetSession)
|
||||
router.Post("/sessions", aiChatController.CreateSession)
|
||||
router.Put("/sessions/:id", aiChatController.UpdateSession)
|
||||
router.Delete("/sessions/:id", aiChatController.DeleteSession)
|
||||
|
||||
// Messages routes
|
||||
router.Get("/sessions/:sessionId/messages", aiChatController.GetSessionMessages)
|
||||
router.Post("/sessions/:sessionId/messages", aiChatController.SendMessage)
|
||||
router.Put("/sessions/:sessionId/messages/:messageId", aiChatController.UpdateMessage)
|
||||
router.Delete("/sessions/:sessionId/messages/:messageId", aiChatController.DeleteMessage)
|
||||
|
||||
// Logs routes
|
||||
router.Get("/logs", aiChatController.GetUserLogs)
|
||||
router.Get("/logs/:id", aiChatController.GetLog)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
package chat_history
|
||||
|
||||
import (
|
||||
"narasi-ahli-be/app/module/chat_history/controller"
|
||||
"narasi-ahli-be/app/module/chat_history/repository"
|
||||
"narasi-ahli-be/app/module/chat_history/service"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
// struct of ChatHistoryRouter
|
||||
type ChatHistoryRouter struct {
|
||||
App fiber.Router
|
||||
Controller controller.ChatHistoryController
|
||||
}
|
||||
|
||||
// register bulky of Chat History module
|
||||
var NewChatHistoryModule = fx.Options(
|
||||
// register repository of Chat History module
|
||||
fx.Provide(repository.NewChatHistoryRepository),
|
||||
|
||||
// register service of Chat History module
|
||||
fx.Provide(service.NewChatHistoryService),
|
||||
|
||||
// register controller of Chat History module
|
||||
fx.Provide(controller.NewChatHistoryController),
|
||||
|
||||
// register router of Chat History module
|
||||
fx.Provide(NewChatHistoryRouter),
|
||||
)
|
||||
|
||||
// init ChatHistoryRouter
|
||||
func NewChatHistoryRouter(fiber *fiber.App, controller controller.ChatHistoryController) *ChatHistoryRouter {
|
||||
return &ChatHistoryRouter{
|
||||
App: fiber,
|
||||
Controller: controller,
|
||||
}
|
||||
}
|
||||
|
||||
// register routes of Chat History module
|
||||
func (_i *ChatHistoryRouter) RegisterChatHistoryRoutes() {
|
||||
// define controllers
|
||||
chatHistoryController := _i.Controller
|
||||
|
||||
// define routes
|
||||
_i.App.Route("/chat-history", func(router fiber.Router) {
|
||||
// Sessions routes
|
||||
router.Get("/sessions", chatHistoryController.GetUserSessions)
|
||||
router.Get("/sessions/:sessionId", chatHistoryController.GetSession)
|
||||
router.Post("/sessions", chatHistoryController.CreateSession)
|
||||
router.Put("/sessions/:sessionId", chatHistoryController.UpdateSession)
|
||||
router.Delete("/sessions/:sessionId", chatHistoryController.DeleteSession)
|
||||
|
||||
// Messages routes
|
||||
router.Get("/sessions/:sessionId/messages", chatHistoryController.GetSessionMessages)
|
||||
router.Post("/sessions/:sessionId/messages", chatHistoryController.CreateMessage)
|
||||
router.Put("/messages/:messageId", chatHistoryController.UpdateMessage)
|
||||
router.Delete("/messages/:messageId", chatHistoryController.DeleteMessage)
|
||||
|
||||
// Combined operations
|
||||
router.Post("/save", chatHistoryController.SaveChatHistory)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,424 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"narasi-ahli-be/app/module/chat_history/request"
|
||||
"narasi-ahli-be/app/module/chat_history/service"
|
||||
"narasi-ahli-be/utils/paginator"
|
||||
utilRes "narasi-ahli-be/utils/response"
|
||||
utilVal "narasi-ahli-be/utils/validator"
|
||||
"strconv"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type chatHistoryController struct {
|
||||
chatHistoryService service.ChatHistoryService
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
type ChatHistoryController interface {
|
||||
// Chat History Sessions
|
||||
GetUserSessions(c *fiber.Ctx) error
|
||||
GetSession(c *fiber.Ctx) error
|
||||
CreateSession(c *fiber.Ctx) error
|
||||
UpdateSession(c *fiber.Ctx) error
|
||||
DeleteSession(c *fiber.Ctx) error
|
||||
|
||||
// Chat History Messages
|
||||
GetSessionMessages(c *fiber.Ctx) error
|
||||
CreateMessage(c *fiber.Ctx) error
|
||||
UpdateMessage(c *fiber.Ctx) error
|
||||
DeleteMessage(c *fiber.Ctx) error
|
||||
|
||||
// Combined operations
|
||||
SaveChatHistory(c *fiber.Ctx) error
|
||||
}
|
||||
|
||||
func NewChatHistoryController(chatHistoryService service.ChatHistoryService, log zerolog.Logger) ChatHistoryController {
|
||||
return &chatHistoryController{
|
||||
chatHistoryService: chatHistoryService,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// Get User Sessions
|
||||
// @Summary Get user chat history sessions
|
||||
// @Description API for getting all chat history sessions for authenticated user
|
||||
// @Tags Chat History
|
||||
// @Security Bearer
|
||||
// @Param X-Client-Key header string false "Insert the X-Client-Key"
|
||||
// @Param req query request.ChatHistorySessionsQueryRequest false "query parameters"
|
||||
// @Param req query paginator.Pagination false "pagination parameters"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /chat-history/sessions [get]
|
||||
func (_i *chatHistoryController) GetUserSessions(c *fiber.Ctx) error {
|
||||
paginate, err := paginator.Paginate(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
reqContext := request.ChatHistorySessionsQueryRequestContext{
|
||||
AgentID: c.Query("agent_id"),
|
||||
SessionID: c.Query("session_id"),
|
||||
}
|
||||
req := reqContext.ToParamRequest()
|
||||
req.Pagination = paginate
|
||||
|
||||
sessionsData, paging, err := _i.chatHistoryService.GetUserSessions(authHeader, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Chat history sessions successfully retrieved"},
|
||||
Data: sessionsData,
|
||||
Meta: paging,
|
||||
})
|
||||
}
|
||||
|
||||
// Get Session
|
||||
// @Summary Get one chat history session with messages
|
||||
// @Description API for getting one chat history session with all its messages
|
||||
// @Tags Chat History
|
||||
// @Security Bearer
|
||||
// @Param X-Client-Key header string false "Insert the X-Client-Key"
|
||||
// @Param sessionId path string true "Session ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /chat-history/sessions/{sessionId} [get]
|
||||
func (_i *chatHistoryController) GetSession(c *fiber.Ctx) error {
|
||||
sessionID := c.Params("sessionId")
|
||||
if sessionID == "" {
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: false,
|
||||
Messages: utilRes.Messages{"Session ID is required"},
|
||||
})
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
sessionData, err := _i.chatHistoryService.GetSession(authHeader, sessionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Chat history session successfully retrieved"},
|
||||
Data: sessionData,
|
||||
})
|
||||
}
|
||||
|
||||
// Create Session
|
||||
// @Summary Create chat history session
|
||||
// @Description API for create chat history session
|
||||
// @Tags Chat History
|
||||
// @Security Bearer
|
||||
// @Param X-Client-Key header string false "Insert the X-Client-Key"
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param payload body request.ChatHistorySessionsCreateRequest true "Required payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /chat-history/sessions [post]
|
||||
func (_i *chatHistoryController) CreateSession(c *fiber.Ctx) error {
|
||||
req := new(request.ChatHistorySessionsCreateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
dataResult, err := _i.chatHistoryService.CreateSession(authHeader, *req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Chat history session successfully created"},
|
||||
Data: dataResult,
|
||||
})
|
||||
}
|
||||
|
||||
// Update Session
|
||||
// @Summary Update chat history session
|
||||
// @Description API for update chat history session
|
||||
// @Tags Chat History
|
||||
// @Security Bearer
|
||||
// @Param X-Client-Key header string false "Insert the X-Client-Key"
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param sessionId path string true "Session ID"
|
||||
// @Param payload body request.ChatHistorySessionsUpdateRequest true "Required payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /chat-history/sessions/{sessionId} [put]
|
||||
func (_i *chatHistoryController) UpdateSession(c *fiber.Ctx) error {
|
||||
sessionID := c.Params("sessionId")
|
||||
if sessionID == "" {
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: false,
|
||||
Messages: utilRes.Messages{"Session ID is required"},
|
||||
})
|
||||
}
|
||||
|
||||
req := new(request.ChatHistorySessionsUpdateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
err := _i.chatHistoryService.UpdateSession(authHeader, sessionID, *req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Chat history session successfully updated"},
|
||||
})
|
||||
}
|
||||
|
||||
// Delete Session
|
||||
// @Summary Delete chat history session
|
||||
// @Description API for delete chat history session
|
||||
// @Tags Chat History
|
||||
// @Security Bearer
|
||||
// @Param X-Client-Key header string false "Insert the X-Client-Key"
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param sessionId path string true "Session ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /chat-history/sessions/{sessionId} [delete]
|
||||
func (_i *chatHistoryController) DeleteSession(c *fiber.Ctx) error {
|
||||
sessionID := c.Params("sessionId")
|
||||
if sessionID == "" {
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: false,
|
||||
Messages: utilRes.Messages{"Session ID is required"},
|
||||
})
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
err := _i.chatHistoryService.DeleteSession(authHeader, sessionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Chat history session successfully deleted"},
|
||||
})
|
||||
}
|
||||
|
||||
// Get Session Messages
|
||||
// @Summary Get chat history session messages
|
||||
// @Description API for getting all messages in a chat history session
|
||||
// @Tags Chat History
|
||||
// @Security Bearer
|
||||
// @Param X-Client-Key header string false "Insert the X-Client-Key"
|
||||
// @Param sessionId path string true "Session ID"
|
||||
// @Param req query request.ChatHistoryMessagesQueryRequest false "query parameters"
|
||||
// @Param req query paginator.Pagination false "pagination parameters"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /chat-history/sessions/{sessionId}/messages [get]
|
||||
func (_i *chatHistoryController) GetSessionMessages(c *fiber.Ctx) error {
|
||||
sessionID := c.Params("sessionId")
|
||||
if sessionID == "" {
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: false,
|
||||
Messages: utilRes.Messages{"Session ID is required"},
|
||||
})
|
||||
}
|
||||
|
||||
paginate, err := paginator.Paginate(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
req := request.ChatHistoryMessagesQueryRequest{
|
||||
SessionID: sessionID,
|
||||
Pagination: paginate,
|
||||
}
|
||||
|
||||
messagesData, paging, err := _i.chatHistoryService.GetSessionMessages(authHeader, sessionID, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Chat history messages successfully retrieved"},
|
||||
Data: messagesData,
|
||||
Meta: paging,
|
||||
})
|
||||
}
|
||||
|
||||
// Create Message
|
||||
// @Summary Create chat history message
|
||||
// @Description API for creating a message in a chat history session
|
||||
// @Tags Chat History
|
||||
// @Security Bearer
|
||||
// @Param X-Client-Key header string false "Insert the X-Client-Key"
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param sessionId path string true "Session ID"
|
||||
// @Param payload body request.ChatHistoryMessagesCreateRequest true "Required payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /chat-history/sessions/{sessionId}/messages [post]
|
||||
func (_i *chatHistoryController) CreateMessage(c *fiber.Ctx) error {
|
||||
sessionID := c.Params("sessionId")
|
||||
if sessionID == "" {
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: false,
|
||||
Messages: utilRes.Messages{"Session ID is required"},
|
||||
})
|
||||
}
|
||||
|
||||
req := new(request.ChatHistoryMessagesCreateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set session ID from URL parameter
|
||||
req.SessionID = sessionID
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
dataResult, err := _i.chatHistoryService.CreateMessage(authHeader, *req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Chat history message successfully created"},
|
||||
Data: dataResult,
|
||||
})
|
||||
}
|
||||
|
||||
// Update Message
|
||||
// @Summary Update chat history message
|
||||
// @Description API for update chat history message
|
||||
// @Tags Chat History
|
||||
// @Security Bearer
|
||||
// @Param X-Client-Key header string false "Insert the X-Client-Key"
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param messageId path int true "Message ID"
|
||||
// @Param payload body request.ChatHistoryMessagesUpdateRequest true "Required payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /chat-history/messages/{messageId} [put]
|
||||
func (_i *chatHistoryController) UpdateMessage(c *fiber.Ctx) error {
|
||||
messageId, err := strconv.ParseUint(c.Params("messageId"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := new(request.ChatHistoryMessagesUpdateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
if err := _i.chatHistoryService.UpdateMessage(authHeader, uint(messageId), *req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Chat history message successfully updated"},
|
||||
})
|
||||
}
|
||||
|
||||
// Delete Message
|
||||
// @Summary Delete chat history message
|
||||
// @Description API for delete chat history message
|
||||
// @Tags Chat History
|
||||
// @Security Bearer
|
||||
// @Param X-Client-Key header string false "Insert the X-Client-Key"
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param messageId path int true "Message ID"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /chat-history/messages/{messageId} [delete]
|
||||
func (_i *chatHistoryController) DeleteMessage(c *fiber.Ctx) error {
|
||||
messageId, err := strconv.ParseUint(c.Params("messageId"), 10, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
if err := _i.chatHistoryService.DeleteMessage(authHeader, uint(messageId)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Chat history message successfully deleted"},
|
||||
})
|
||||
}
|
||||
|
||||
// Save Chat History
|
||||
// @Summary Save chat history (sessions and messages)
|
||||
// @Description API for saving complete chat history including sessions and messages
|
||||
// @Tags Chat History
|
||||
// @Security Bearer
|
||||
// @Param X-Client-Key header string false "Insert the X-Client-Key"
|
||||
// @Param X-Csrf-Token header string true "Insert the X-Csrf-Token"
|
||||
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
|
||||
// @Param payload body request.ChatHistorySessionsCreateRequest true "Required payload"
|
||||
// @Success 200 {object} response.Response
|
||||
// @Failure 400 {object} response.BadRequestError
|
||||
// @Failure 401 {object} response.UnauthorizedError
|
||||
// @Failure 500 {object} response.InternalServerError
|
||||
// @Router /chat-history/save [post]
|
||||
func (_i *chatHistoryController) SaveChatHistory(c *fiber.Ctx) error {
|
||||
req := new(request.ChatHistorySessionsCreateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
|
||||
dataResult, err := _i.chatHistoryService.SaveChatHistory(authHeader, *req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Chat history saved successfully"},
|
||||
Data: dataResult,
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package mapper
|
||||
|
||||
import (
|
||||
"narasi-ahli-be/app/database/entity"
|
||||
"narasi-ahli-be/app/module/chat_history/response"
|
||||
)
|
||||
|
||||
// Chat History Sessions Mapper
|
||||
func ChatHistorySessionsResponseMapper(entity *entity.ChatSessions) *response.ChatHistorySessionsResponse {
|
||||
if entity == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &response.ChatHistorySessionsResponse{
|
||||
ID: entity.ID,
|
||||
SessionID: entity.SessionID,
|
||||
UserID: entity.UserID,
|
||||
AgentID: entity.AgentID,
|
||||
Title: entity.Title,
|
||||
MessageCount: entity.MessageCount,
|
||||
Status: entity.Status,
|
||||
CreatedAt: entity.CreatedAt,
|
||||
UpdatedAt: entity.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// Chat History Messages Mapper
|
||||
func ChatHistoryMessagesResponseMapper(entity *entity.ChatMessagesNew) *response.ChatHistoryMessagesResponse {
|
||||
if entity == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &response.ChatHistoryMessagesResponse{
|
||||
ID: entity.ID,
|
||||
SessionID: entity.SessionID,
|
||||
MessageType: entity.MessageType,
|
||||
Content: entity.Content,
|
||||
CreatedAt: entity.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// Chat History Session with Messages Mapper
|
||||
func ChatHistorySessionWithMessagesResponseMapper(session *entity.ChatSessions, messages []*entity.ChatMessagesNew) *response.ChatHistorySessionWithMessagesResponse {
|
||||
if session == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sessionResponse := ChatHistorySessionsResponseMapper(session)
|
||||
|
||||
var messagesResponse []response.ChatHistoryMessagesResponse
|
||||
for _, message := range messages {
|
||||
if message != nil {
|
||||
messagesResponse = append(messagesResponse, *ChatHistoryMessagesResponseMapper(message))
|
||||
}
|
||||
}
|
||||
|
||||
return &response.ChatHistorySessionWithMessagesResponse{
|
||||
Session: *sessionResponse,
|
||||
Messages: messagesResponse,
|
||||
}
|
||||
}
|
||||
|
||||
// Chat History List Mapper
|
||||
func ChatHistoryListResponseMapper(sessions []*entity.ChatSessions) *response.ChatHistoryListResponse {
|
||||
var sessionsResponse []response.ChatHistorySessionsResponse
|
||||
|
||||
for _, session := range sessions {
|
||||
if session != nil {
|
||||
sessionsResponse = append(sessionsResponse, *ChatHistorySessionsResponseMapper(session))
|
||||
}
|
||||
}
|
||||
|
||||
return &response.ChatHistoryListResponse{
|
||||
Sessions: sessionsResponse,
|
||||
Total: len(sessionsResponse),
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"narasi-ahli-be/app/database"
|
||||
"narasi-ahli-be/app/database/entity"
|
||||
"narasi-ahli-be/app/module/chat_history/request"
|
||||
"narasi-ahli-be/utils/paginator"
|
||||
)
|
||||
|
||||
type chatHistoryRepository struct {
|
||||
DB *database.Database
|
||||
}
|
||||
|
||||
type ChatHistoryRepository interface {
|
||||
// Chat Sessions
|
||||
GetUserSessions(userId uint, req request.ChatHistorySessionsQueryRequest) (sessions []*entity.ChatSessions, paging paginator.Pagination, err error)
|
||||
FindSessionByID(id uint) (session *entity.ChatSessions, err error)
|
||||
FindSessionBySessionID(sessionID string) (session *entity.ChatSessions, err error)
|
||||
FindSessionByUserAndSessionID(userID uint, sessionID string) (session *entity.ChatSessions, err error)
|
||||
CreateSession(session *entity.ChatSessions) (result *entity.ChatSessions, err error)
|
||||
UpdateSession(sessionID string, session *entity.ChatSessions) (err error)
|
||||
DeleteSession(sessionID string) (err error)
|
||||
IncrementMessageCount(sessionID string) (err error)
|
||||
|
||||
// Chat Messages
|
||||
GetSessionMessages(sessionID string, req request.ChatHistoryMessagesQueryRequest) (messages []*entity.ChatMessagesNew, paging paginator.Pagination, err error)
|
||||
CreateMessage(message *entity.ChatMessagesNew) (result *entity.ChatMessagesNew, err error)
|
||||
UpdateMessage(messageID uint, message *entity.ChatMessagesNew) (err error)
|
||||
DeleteMessage(messageID uint) (err error)
|
||||
DeleteMessagesBySessionID(sessionID string) (err error)
|
||||
GetLastMessage(sessionID string) (message *entity.ChatMessagesNew, err error)
|
||||
}
|
||||
|
||||
func NewChatHistoryRepository(db *database.Database) ChatHistoryRepository {
|
||||
return &chatHistoryRepository{
|
||||
DB: db,
|
||||
}
|
||||
}
|
||||
|
||||
// Chat Sessions methods
|
||||
func (_i *chatHistoryRepository) GetUserSessions(userId uint, req request.ChatHistorySessionsQueryRequest) (sessions []*entity.ChatSessions, paging paginator.Pagination, err error) {
|
||||
query := _i.DB.DB.Where("user_id = ?", userId)
|
||||
|
||||
// Apply filters
|
||||
if req.AgentID != nil {
|
||||
query = query.Where("agent_id = ?", *req.AgentID)
|
||||
}
|
||||
if req.SessionID != nil {
|
||||
query = query.Where("session_id = ?", *req.SessionID)
|
||||
}
|
||||
|
||||
// Include user relationship
|
||||
query = query.Preload("User")
|
||||
|
||||
// Order by updated_at desc (most recent first)
|
||||
query = query.Order("updated_at DESC")
|
||||
|
||||
// Apply pagination
|
||||
var count int64
|
||||
query.Count(&count)
|
||||
req.Pagination.Count = count
|
||||
req.Pagination = paginator.Paging(req.Pagination)
|
||||
|
||||
err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&sessions).Error
|
||||
paging = *req.Pagination
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *chatHistoryRepository) FindSessionByID(id uint) (session *entity.ChatSessions, err error) {
|
||||
err = _i.DB.DB.Where("id = ?", id).Preload("User").First(&session).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *chatHistoryRepository) FindSessionBySessionID(sessionID string) (session *entity.ChatSessions, err error) {
|
||||
err = _i.DB.DB.Where("session_id = ?", sessionID).Preload("User").First(&session).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *chatHistoryRepository) FindSessionByUserAndSessionID(userID uint, sessionID string) (session *entity.ChatSessions, err error) {
|
||||
err = _i.DB.DB.Where("user_id = ? AND session_id = ?", userID, sessionID).Preload("User").First(&session).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *chatHistoryRepository) CreateSession(session *entity.ChatSessions) (result *entity.ChatSessions, err error) {
|
||||
err = _i.DB.DB.Create(session).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Reload with relationships
|
||||
err = _i.DB.DB.Preload("User").First(&result, session.ID).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *chatHistoryRepository) UpdateSession(sessionID string, session *entity.ChatSessions) (err error) {
|
||||
err = _i.DB.DB.Where("session_id = ?", sessionID).Updates(session).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *chatHistoryRepository) DeleteSession(sessionID string) (err error) {
|
||||
err = _i.DB.DB.Where("session_id = ?", sessionID).Delete(&entity.ChatSessions{}).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *chatHistoryRepository) IncrementMessageCount(sessionID string) (err error) {
|
||||
err = _i.DB.DB.Exec("UPDATE chat_sessions SET message_count = message_count + 1 WHERE session_id = ?", sessionID).Error
|
||||
return
|
||||
}
|
||||
|
||||
// Chat Messages methods
|
||||
func (_i *chatHistoryRepository) GetSessionMessages(sessionID string, req request.ChatHistoryMessagesQueryRequest) (messages []*entity.ChatMessagesNew, paging paginator.Pagination, err error) {
|
||||
query := _i.DB.DB.Where("session_id = ?", sessionID)
|
||||
|
||||
// Order by created_at asc (oldest first for chat)
|
||||
query = query.Order("created_at ASC")
|
||||
|
||||
// Apply pagination
|
||||
var count int64
|
||||
query.Count(&count)
|
||||
req.Pagination.Count = count
|
||||
req.Pagination = paginator.Paging(req.Pagination)
|
||||
|
||||
err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&messages).Error
|
||||
paging = *req.Pagination
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *chatHistoryRepository) CreateMessage(message *entity.ChatMessagesNew) (result *entity.ChatMessagesNew, err error) {
|
||||
err = _i.DB.DB.Create(message).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Reload
|
||||
err = _i.DB.DB.First(&result, message.ID).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *chatHistoryRepository) UpdateMessage(messageID uint, message *entity.ChatMessagesNew) (err error) {
|
||||
err = _i.DB.DB.Model(&entity.ChatMessagesNew{}).Where("id = ?", messageID).Updates(message).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *chatHistoryRepository) DeleteMessage(messageID uint) (err error) {
|
||||
err = _i.DB.DB.Where("id = ?", messageID).Delete(&entity.ChatMessagesNew{}).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *chatHistoryRepository) DeleteMessagesBySessionID(sessionID string) (err error) {
|
||||
err = _i.DB.DB.Where("session_id = ?", sessionID).Delete(&entity.ChatMessagesNew{}).Error
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *chatHistoryRepository) GetLastMessage(sessionID string) (message *entity.ChatMessagesNew, err error) {
|
||||
err = _i.DB.DB.Where("session_id = ?", sessionID).Order("created_at DESC").First(&message).Error
|
||||
return
|
||||
}
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
package request
|
||||
|
||||
import (
|
||||
"narasi-ahli-be/app/database/entity"
|
||||
"narasi-ahli-be/utils/paginator"
|
||||
)
|
||||
|
||||
// Chat History Sessions Request DTOs
|
||||
type ChatHistorySessionsQueryRequest struct {
|
||||
UserID *uint `json:"user_id"`
|
||||
AgentID *string `json:"agent_id"`
|
||||
SessionID *string `json:"session_id"`
|
||||
Pagination *paginator.Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type ChatHistorySessionsCreateRequest struct {
|
||||
UserID uint `json:"user_id" validate:"required"`
|
||||
AgentID string `json:"agent_id" validate:"required"`
|
||||
SessionID string `json:"session_id" validate:"required"`
|
||||
Title *string `json:"title"`
|
||||
Messages []ChatMessageRequest `json:"messages"`
|
||||
}
|
||||
|
||||
type ChatMessageRequest struct {
|
||||
Type string `json:"type" validate:"required,oneof=user assistant"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
}
|
||||
|
||||
func (req ChatHistorySessionsCreateRequest) ToEntity() *entity.ChatSessions {
|
||||
title := "Chat Session"
|
||||
if req.Title != nil {
|
||||
title = *req.Title
|
||||
}
|
||||
|
||||
return &entity.ChatSessions{
|
||||
SessionID: req.SessionID,
|
||||
UserID: req.UserID,
|
||||
AgentID: req.AgentID,
|
||||
Title: title,
|
||||
MessageCount: len(req.Messages),
|
||||
Status: "active",
|
||||
}
|
||||
}
|
||||
|
||||
type ChatHistorySessionsUpdateRequest struct {
|
||||
Title *string `json:"title"`
|
||||
Status *string `json:"status" validate:"omitempty,oneof=active archived deleted"`
|
||||
}
|
||||
|
||||
func (req ChatHistorySessionsUpdateRequest) ToEntity() *entity.ChatSessions {
|
||||
entity := &entity.ChatSessions{}
|
||||
|
||||
if req.Title != nil {
|
||||
entity.Title = *req.Title
|
||||
}
|
||||
if req.Status != nil {
|
||||
entity.Status = *req.Status
|
||||
}
|
||||
|
||||
return entity
|
||||
}
|
||||
|
||||
// Chat History Messages Request DTOs
|
||||
type ChatHistoryMessagesQueryRequest struct {
|
||||
SessionID string `json:"session_id" validate:"required"`
|
||||
Pagination *paginator.Pagination `json:"pagination"`
|
||||
}
|
||||
|
||||
type ChatHistoryMessagesCreateRequest struct {
|
||||
SessionID string `json:"session_id" validate:"required"`
|
||||
MessageType string `json:"message_type" validate:"required,oneof=user assistant"`
|
||||
Content string `json:"content" validate:"required"`
|
||||
}
|
||||
|
||||
func (req ChatHistoryMessagesCreateRequest) ToEntity() *entity.ChatMessagesNew {
|
||||
return &entity.ChatMessagesNew{
|
||||
SessionID: req.SessionID,
|
||||
MessageType: req.MessageType,
|
||||
Content: req.Content,
|
||||
}
|
||||
}
|
||||
|
||||
type ChatHistoryMessagesUpdateRequest struct {
|
||||
Content string `json:"content" validate:"required"`
|
||||
}
|
||||
|
||||
func (req ChatHistoryMessagesUpdateRequest) ToEntity() *entity.ChatMessagesNew {
|
||||
return &entity.ChatMessagesNew{
|
||||
Content: req.Content,
|
||||
}
|
||||
}
|
||||
|
||||
// Context Request DTOs for query parameters
|
||||
type ChatHistorySessionsQueryRequestContext struct {
|
||||
UserID string `json:"user_id"`
|
||||
AgentID string `json:"agent_id"`
|
||||
SessionID string `json:"session_id"`
|
||||
}
|
||||
|
||||
func (req ChatHistorySessionsQueryRequestContext) ToParamRequest() ChatHistorySessionsQueryRequest {
|
||||
var request ChatHistorySessionsQueryRequest
|
||||
|
||||
if userIDStr := req.UserID; userIDStr != "" {
|
||||
// Parse user ID from string to uint
|
||||
// This will be handled in the controller
|
||||
}
|
||||
|
||||
if agentID := req.AgentID; agentID != "" {
|
||||
request.AgentID = &agentID
|
||||
}
|
||||
|
||||
if sessionID := req.SessionID; sessionID != "" {
|
||||
request.SessionID = &sessionID
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
||||
type ChatHistoryMessagesQueryRequestContext struct {
|
||||
SessionID string `json:"session_id"`
|
||||
}
|
||||
|
||||
func (req ChatHistoryMessagesQueryRequestContext) ToParamRequest() ChatHistoryMessagesQueryRequest {
|
||||
var request ChatHistoryMessagesQueryRequest
|
||||
|
||||
if sessionID := req.SessionID; sessionID != "" {
|
||||
request.SessionID = sessionID
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package response
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Chat History Sessions Response DTOs
|
||||
type ChatHistorySessionsResponse struct {
|
||||
ID uint `json:"id"`
|
||||
SessionID string `json:"session_id"`
|
||||
UserID uint `json:"user_id"`
|
||||
AgentID string `json:"agent_id"`
|
||||
Title string `json:"title"`
|
||||
MessageCount int `json:"message_count"`
|
||||
Status string `json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// Chat History Messages Response DTOs
|
||||
type ChatHistoryMessagesResponse struct {
|
||||
ID uint `json:"id"`
|
||||
SessionID string `json:"session_id"`
|
||||
MessageType string `json:"message_type"`
|
||||
Content string `json:"content"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// Combined Response for Session with Messages
|
||||
type ChatHistorySessionWithMessagesResponse struct {
|
||||
Session ChatHistorySessionsResponse `json:"session"`
|
||||
Messages []ChatHistoryMessagesResponse `json:"messages"`
|
||||
}
|
||||
|
||||
// Chat History List Response
|
||||
type ChatHistoryListResponse struct {
|
||||
Sessions []ChatHistorySessionsResponse `json:"sessions"`
|
||||
Total int `json:"total"`
|
||||
}
|
||||
|
|
@ -0,0 +1,317 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"narasi-ahli-be/app/database/entity"
|
||||
"narasi-ahli-be/app/module/chat_history/mapper"
|
||||
"narasi-ahli-be/app/module/chat_history/repository"
|
||||
"narasi-ahli-be/app/module/chat_history/request"
|
||||
"narasi-ahli-be/app/module/chat_history/response"
|
||||
usersRepository "narasi-ahli-be/app/module/users/repository"
|
||||
"narasi-ahli-be/utils/paginator"
|
||||
utilSvc "narasi-ahli-be/utils/service"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type chatHistoryService struct {
|
||||
Repo repository.ChatHistoryRepository
|
||||
UsersRepo usersRepository.UsersRepository
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
type ChatHistoryService interface {
|
||||
// Sessions
|
||||
GetUserSessions(authToken string, req request.ChatHistorySessionsQueryRequest) (sessions []*response.ChatHistorySessionsResponse, paging paginator.Pagination, err error)
|
||||
GetSession(authToken string, sessionID string) (session *response.ChatHistorySessionWithMessagesResponse, err error)
|
||||
CreateSession(authToken string, req request.ChatHistorySessionsCreateRequest) (session *response.ChatHistorySessionsResponse, err error)
|
||||
UpdateSession(authToken string, sessionID string, req request.ChatHistorySessionsUpdateRequest) (err error)
|
||||
DeleteSession(authToken string, sessionID string) error
|
||||
|
||||
// Messages
|
||||
GetSessionMessages(authToken string, sessionID string, req request.ChatHistoryMessagesQueryRequest) (messages []*response.ChatHistoryMessagesResponse, paging paginator.Pagination, err error)
|
||||
CreateMessage(authToken string, req request.ChatHistoryMessagesCreateRequest) (message *response.ChatHistoryMessagesResponse, err error)
|
||||
UpdateMessage(authToken string, messageID uint, req request.ChatHistoryMessagesUpdateRequest) (err error)
|
||||
DeleteMessage(authToken string, messageID uint) error
|
||||
|
||||
// Combined operations
|
||||
SaveChatHistory(authToken string, req request.ChatHistorySessionsCreateRequest) (session *response.ChatHistorySessionsResponse, err error)
|
||||
}
|
||||
|
||||
func NewChatHistoryService(repo repository.ChatHistoryRepository, usersRepo usersRepository.UsersRepository, log zerolog.Logger) ChatHistoryService {
|
||||
return &chatHistoryService{
|
||||
Repo: repo,
|
||||
UsersRepo: usersRepo,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// Sessions methods
|
||||
func (_i *chatHistoryService) GetUserSessions(authToken string, req request.ChatHistorySessionsQueryRequest) (sessions []*response.ChatHistorySessionsResponse, paging paginator.Pagination, err error) {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
|
||||
// Set user ID from auth token
|
||||
req.UserID = &userInfo.ID
|
||||
|
||||
results, paging, err := _i.Repo.GetUserSessions(userInfo.ID, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
sessions = append(sessions, mapper.ChatHistorySessionsResponseMapper(result))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *chatHistoryService) GetSession(authToken string, sessionID string) (session *response.ChatHistorySessionWithMessagesResponse, err error) {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
|
||||
// Verify session belongs to user
|
||||
sessionEntity, err := _i.Repo.FindSessionByUserAndSessionID(userInfo.ID, sessionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get messages for this session
|
||||
messagesReq := request.ChatHistoryMessagesQueryRequest{
|
||||
SessionID: sessionID,
|
||||
Pagination: &paginator.Pagination{
|
||||
Limit: 1000, // Get all messages for now
|
||||
},
|
||||
}
|
||||
messages, _, err := _i.Repo.GetSessionMessages(sessionID, messagesReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mapper.ChatHistorySessionWithMessagesResponseMapper(sessionEntity, messages), nil
|
||||
}
|
||||
|
||||
func (_i *chatHistoryService) CreateSession(authToken string, req request.ChatHistorySessionsCreateRequest) (session *response.ChatHistorySessionsResponse, err error) {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
|
||||
// Set user ID from auth token
|
||||
req.UserID = userInfo.ID
|
||||
|
||||
entity := req.ToEntity()
|
||||
|
||||
result, err := _i.Repo.CreateSession(entity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mapper.ChatHistorySessionsResponseMapper(result), nil
|
||||
}
|
||||
|
||||
func (_i *chatHistoryService) UpdateSession(authToken string, sessionID string, req request.ChatHistorySessionsUpdateRequest) (err error) {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
_i.Log.Info().Interface("data", req).Msg("Updating chat history session")
|
||||
|
||||
// Check if session exists and belongs to user
|
||||
existing, err := _i.Repo.FindSessionByUserAndSessionID(userInfo.ID, sessionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existing == nil {
|
||||
return errors.New("chat history session not found")
|
||||
}
|
||||
|
||||
entity := req.ToEntity()
|
||||
return _i.Repo.UpdateSession(sessionID, entity)
|
||||
}
|
||||
|
||||
func (_i *chatHistoryService) DeleteSession(authToken string, sessionID string) error {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
_i.Log.Info().Uint("userId", userInfo.ID).Str("sessionId", sessionID).Msg("Deleting chat history session")
|
||||
|
||||
// Check if session exists and belongs to user
|
||||
existing, err := _i.Repo.FindSessionByUserAndSessionID(userInfo.ID, sessionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if existing == nil {
|
||||
return errors.New("chat history session not found")
|
||||
}
|
||||
|
||||
return _i.Repo.DeleteSession(sessionID)
|
||||
}
|
||||
|
||||
// Messages methods
|
||||
func (_i *chatHistoryService) GetSessionMessages(authToken string, sessionID string, req request.ChatHistoryMessagesQueryRequest) (messages []*response.ChatHistoryMessagesResponse, paging paginator.Pagination, err error) {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
|
||||
// Verify session belongs to user
|
||||
_, err = _i.Repo.FindSessionByUserAndSessionID(userInfo.ID, sessionID)
|
||||
if err != nil {
|
||||
return nil, paginator.Pagination{}, err
|
||||
}
|
||||
|
||||
results, paging, err := _i.Repo.GetSessionMessages(sessionID, req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
messages = append(messages, mapper.ChatHistoryMessagesResponseMapper(result))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (_i *chatHistoryService) CreateMessage(authToken string, req request.ChatHistoryMessagesCreateRequest) (message *response.ChatHistoryMessagesResponse, err error) {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
_i.Log.Info().Interface("data", req).Msg("Creating chat history message")
|
||||
|
||||
// Verify session belongs to user
|
||||
_, err = _i.Repo.FindSessionByUserAndSessionID(userInfo.ID, req.SessionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entity := req.ToEntity()
|
||||
|
||||
result, err := _i.Repo.CreateMessage(entity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Increment message count in session
|
||||
err = _i.Repo.IncrementMessageCount(req.SessionID)
|
||||
if err != nil {
|
||||
_i.Log.Error().Err(err).Msg("Failed to increment message count")
|
||||
}
|
||||
|
||||
return mapper.ChatHistoryMessagesResponseMapper(result), nil
|
||||
}
|
||||
|
||||
func (_i *chatHistoryService) UpdateMessage(authToken string, messageID uint, req request.ChatHistoryMessagesUpdateRequest) (err error) {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
|
||||
// Get message to verify it belongs to user's session
|
||||
messages, _, err := _i.Repo.GetSessionMessages("", request.ChatHistoryMessagesQueryRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find the specific message
|
||||
var targetMessage *entity.ChatMessagesNew
|
||||
for _, msg := range messages {
|
||||
if msg.ID == messageID {
|
||||
targetMessage = msg
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetMessage == nil {
|
||||
return errors.New("message not found")
|
||||
}
|
||||
|
||||
// Verify session belongs to user
|
||||
_, err = _i.Repo.FindSessionByUserAndSessionID(userInfo.ID, targetMessage.SessionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entity := req.ToEntity()
|
||||
return _i.Repo.UpdateMessage(messageID, entity)
|
||||
}
|
||||
|
||||
func (_i *chatHistoryService) DeleteMessage(authToken string, messageID uint) error {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
|
||||
// Get message to verify it belongs to user's session
|
||||
messages, _, err := _i.Repo.GetSessionMessages("", request.ChatHistoryMessagesQueryRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Find the specific message
|
||||
var targetMessage *entity.ChatMessagesNew
|
||||
for _, msg := range messages {
|
||||
if msg.ID == messageID {
|
||||
targetMessage = msg
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if targetMessage == nil {
|
||||
return errors.New("message not found")
|
||||
}
|
||||
|
||||
// Verify session belongs to user
|
||||
_, err = _i.Repo.FindSessionByUserAndSessionID(userInfo.ID, targetMessage.SessionID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return _i.Repo.DeleteMessage(messageID)
|
||||
}
|
||||
|
||||
// Combined operations
|
||||
func (_i *chatHistoryService) SaveChatHistory(authToken string, req request.ChatHistorySessionsCreateRequest) (session *response.ChatHistorySessionsResponse, err error) {
|
||||
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
_i.Log.Info().Interface("data", req).Msg("Saving chat history")
|
||||
|
||||
// Set user ID from auth token
|
||||
req.UserID = userInfo.ID
|
||||
|
||||
// Check if session already exists
|
||||
existingSession, err := _i.Repo.FindSessionBySessionID(req.SessionID)
|
||||
if err == nil && existingSession != nil {
|
||||
// Update existing session
|
||||
updateReq := request.ChatHistorySessionsUpdateRequest{
|
||||
Title: req.Title,
|
||||
}
|
||||
err = _i.UpdateSession(authToken, req.SessionID, updateReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Delete existing messages
|
||||
err = _i.Repo.DeleteMessagesBySessionID(req.SessionID)
|
||||
if err != nil {
|
||||
_i.Log.Error().Err(err).Msg("Failed to delete existing messages")
|
||||
}
|
||||
} else {
|
||||
// Create new session
|
||||
entity := req.ToEntity()
|
||||
_, err = _i.Repo.CreateSession(entity)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Save messages if provided
|
||||
if len(req.Messages) > 0 {
|
||||
for _, msgReq := range req.Messages {
|
||||
messageEntity := &entity.ChatMessagesNew{
|
||||
SessionID: req.SessionID,
|
||||
MessageType: msgReq.Type,
|
||||
Content: msgReq.Content,
|
||||
}
|
||||
|
||||
_, err = _i.Repo.CreateMessage(messageEntity)
|
||||
if err != nil {
|
||||
_i.Log.Error().Err(err).Msg("Failed to create message")
|
||||
}
|
||||
}
|
||||
|
||||
// Update message count
|
||||
err = _i.Repo.UpdateSession(req.SessionID, &entity.ChatSessions{
|
||||
MessageCount: len(req.Messages),
|
||||
})
|
||||
if err != nil {
|
||||
_i.Log.Error().Err(err).Msg("Failed to update message count")
|
||||
}
|
||||
}
|
||||
|
||||
// Return the session
|
||||
result, err := _i.Repo.FindSessionBySessionID(req.SessionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mapper.ChatHistorySessionsResponseMapper(result), nil
|
||||
}
|
||||
|
|
@ -3,12 +3,14 @@ package router
|
|||
import (
|
||||
"narasi-ahli-be/app/module/activity_logs"
|
||||
"narasi-ahli-be/app/module/advertisement"
|
||||
"narasi-ahli-be/app/module/ai_chat"
|
||||
"narasi-ahli-be/app/module/article_approvals"
|
||||
"narasi-ahli-be/app/module/article_categories"
|
||||
"narasi-ahli-be/app/module/article_category_details"
|
||||
"narasi-ahli-be/app/module/article_comments"
|
||||
"narasi-ahli-be/app/module/article_files"
|
||||
"narasi-ahli-be/app/module/articles"
|
||||
"narasi-ahli-be/app/module/chat_history"
|
||||
"narasi-ahli-be/app/module/cities"
|
||||
"narasi-ahli-be/app/module/custom_static_pages"
|
||||
"narasi-ahli-be/app/module/districts"
|
||||
|
|
@ -39,12 +41,14 @@ type Router struct {
|
|||
|
||||
ActivityLogsRouter *activity_logs.ActivityLogsRouter
|
||||
AdvertisementRouter *advertisement.AdvertisementRouter
|
||||
AIChatRouter *ai_chat.AIChatRouter
|
||||
ArticleCategoriesRouter *article_categories.ArticleCategoriesRouter
|
||||
ArticleCategoryDetailsRouter *article_category_details.ArticleCategoryDetailsRouter
|
||||
ArticleFilesRouter *article_files.ArticleFilesRouter
|
||||
ArticleCommentsRouter *article_comments.ArticleCommentsRouter
|
||||
ArticleApprovalsRouter *article_approvals.ArticleApprovalsRouter
|
||||
ArticlesRouter *articles.ArticlesRouter
|
||||
ChatHistoryRouter *chat_history.ChatHistoryRouter
|
||||
CitiesRouter *cities.CitiesRouter
|
||||
CustomStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter
|
||||
DistrictsRouter *districts.DistrictsRouter
|
||||
|
|
@ -70,12 +74,14 @@ func NewRouter(
|
|||
|
||||
activityLogsRouter *activity_logs.ActivityLogsRouter,
|
||||
advertisementRouter *advertisement.AdvertisementRouter,
|
||||
aiChatRouter *ai_chat.AIChatRouter,
|
||||
articleCategoriesRouter *article_categories.ArticleCategoriesRouter,
|
||||
articleCategoryDetailsRouter *article_category_details.ArticleCategoryDetailsRouter,
|
||||
articleFilesRouter *article_files.ArticleFilesRouter,
|
||||
articleCommentsRouter *article_comments.ArticleCommentsRouter,
|
||||
articleApprovalsRouter *article_approvals.ArticleApprovalsRouter,
|
||||
articlesRouter *articles.ArticlesRouter,
|
||||
chatHistoryRouter *chat_history.ChatHistoryRouter,
|
||||
citiesRouter *cities.CitiesRouter,
|
||||
customStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter,
|
||||
districtsRouter *districts.DistrictsRouter,
|
||||
|
|
@ -99,12 +105,14 @@ func NewRouter(
|
|||
Cfg: cfg,
|
||||
ActivityLogsRouter: activityLogsRouter,
|
||||
AdvertisementRouter: advertisementRouter,
|
||||
AIChatRouter: aiChatRouter,
|
||||
ArticleCategoriesRouter: articleCategoriesRouter,
|
||||
ArticleCategoryDetailsRouter: articleCategoryDetailsRouter,
|
||||
ArticleFilesRouter: articleFilesRouter,
|
||||
ArticleCommentsRouter: articleCommentsRouter,
|
||||
ArticleApprovalsRouter: articleApprovalsRouter,
|
||||
ArticlesRouter: articlesRouter,
|
||||
ChatHistoryRouter: chatHistoryRouter,
|
||||
CitiesRouter: citiesRouter,
|
||||
CustomStaticPagesRouter: customStaticPagesRouter,
|
||||
DistrictsRouter: districtsRouter,
|
||||
|
|
@ -138,12 +146,14 @@ func (r *Router) Register() {
|
|||
// Register routes of modules
|
||||
r.ActivityLogsRouter.RegisterActivityLogsRoutes()
|
||||
r.AdvertisementRouter.RegisterAdvertisementRoutes()
|
||||
r.AIChatRouter.RegisterAIChatRoutes()
|
||||
r.ArticleCategoriesRouter.RegisterArticleCategoriesRoutes()
|
||||
r.ArticleCategoryDetailsRouter.RegisterArticleCategoryDetailsRoutes()
|
||||
r.ArticleFilesRouter.RegisterArticleFilesRoutes()
|
||||
r.ArticleApprovalsRouter.RegisterArticleApprovalsRoutes()
|
||||
r.ArticlesRouter.RegisterArticlesRoutes()
|
||||
r.ArticleCommentsRouter.RegisterArticleCommentsRoutes()
|
||||
r.ChatHistoryRouter.RegisterChatHistoryRoutes()
|
||||
r.CitiesRouter.RegisterCitiesRoutes()
|
||||
r.CustomStaticPagesRouter.RegisterCustomStaticPagesRoutes()
|
||||
r.DistrictsRouter.RegisterDistrictsRoutes()
|
||||
|
|
|
|||
1708
docs/swagger/docs.go
1708
docs/swagger/docs.go
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
4
main.go
4
main.go
|
|
@ -5,12 +5,14 @@ import (
|
|||
"narasi-ahli-be/app/middleware"
|
||||
"narasi-ahli-be/app/module/activity_logs"
|
||||
"narasi-ahli-be/app/module/advertisement"
|
||||
"narasi-ahli-be/app/module/ai_chat"
|
||||
"narasi-ahli-be/app/module/article_approvals"
|
||||
"narasi-ahli-be/app/module/article_categories"
|
||||
"narasi-ahli-be/app/module/article_category_details"
|
||||
"narasi-ahli-be/app/module/article_comments"
|
||||
"narasi-ahli-be/app/module/article_files"
|
||||
"narasi-ahli-be/app/module/articles"
|
||||
"narasi-ahli-be/app/module/chat_history"
|
||||
"narasi-ahli-be/app/module/cities"
|
||||
"narasi-ahli-be/app/module/custom_static_pages"
|
||||
"narasi-ahli-be/app/module/districts"
|
||||
|
|
@ -65,12 +67,14 @@ func main() {
|
|||
// provide modules
|
||||
activity_logs.NewActivityLogsModule,
|
||||
advertisement.NewAdvertisementModule,
|
||||
ai_chat.NewAIChatModule,
|
||||
article_categories.NewArticleCategoriesModule,
|
||||
article_category_details.NewArticleCategoryDetailsModule,
|
||||
article_files.NewArticleFilesModule,
|
||||
article_approvals.NewArticleApprovalsModule,
|
||||
articles.NewArticlesModule,
|
||||
article_comments.NewArticleCommentsModule,
|
||||
chat_history.NewChatHistoryModule,
|
||||
cities.NewCitiesModule,
|
||||
custom_static_pages.NewCustomStaticPagesModule,
|
||||
districts.NewDistrictsModule,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"narasi-ahli-be/app/database"
|
||||
"narasi-ahli-be/app/database/entity"
|
||||
)
|
||||
|
||||
// CreateChatHistoryTables creates the chat history tables
|
||||
func CreateChatHistoryTables(db *database.Database) error {
|
||||
// Auto-migrate the new entities
|
||||
err := db.DB.AutoMigrate(
|
||||
&entity.ChatSessions{},
|
||||
&entity.ChatMessagesNew{},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create indexes manually if needed
|
||||
db.DB.Exec("CREATE INDEX IF NOT EXISTS idx_chat_sessions_user_id ON chat_sessions (user_id)")
|
||||
db.DB.Exec("CREATE INDEX IF NOT EXISTS idx_chat_sessions_agent_id ON chat_sessions (agent_id)")
|
||||
db.DB.Exec("CREATE INDEX IF NOT EXISTS idx_chat_sessions_session_id ON chat_sessions (session_id)")
|
||||
db.DB.Exec("CREATE INDEX IF NOT EXISTS idx_chat_sessions_created_at ON chat_sessions (created_at)")
|
||||
db.DB.Exec("CREATE INDEX IF NOT EXISTS idx_chat_messages_new_session_id ON chat_messages_new (session_id)")
|
||||
db.DB.Exec("CREATE INDEX IF NOT EXISTS idx_chat_messages_new_created_at ON chat_messages_new (created_at)")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
-- Migration: Create Chat History Tables
|
||||
-- Description: Create tables for chat history functionality based on the old web API structure
|
||||
-- Date: 2024-01-01
|
||||
|
||||
-- Create chat_sessions table
|
||||
CREATE TABLE IF NOT EXISTS chat_sessions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
session_id VARCHAR(255) NOT NULL UNIQUE,
|
||||
user_id INTEGER NOT NULL,
|
||||
agent_id VARCHAR(255) NOT NULL,
|
||||
title VARCHAR(255) NOT NULL DEFAULT 'Chat Session',
|
||||
message_count INTEGER DEFAULT 0,
|
||||
status VARCHAR(50) DEFAULT 'active',
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create chat_messages_new table (to avoid conflict with existing chat_messages)
|
||||
CREATE TABLE IF NOT EXISTS chat_messages_new (
|
||||
id SERIAL PRIMARY KEY,
|
||||
session_id VARCHAR(255) NOT NULL,
|
||||
message_type VARCHAR(50) NOT NULL CHECK (message_type IN ('user', 'assistant')),
|
||||
content TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Create indexes for better performance
|
||||
CREATE INDEX IF NOT EXISTS idx_chat_sessions_user_id ON chat_sessions (user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_chat_sessions_agent_id ON chat_sessions (agent_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_chat_sessions_session_id ON chat_sessions (session_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_chat_sessions_created_at ON chat_sessions (created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_chat_messages_new_session_id ON chat_messages_new (session_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_chat_messages_new_created_at ON chat_messages_new (created_at);
|
||||
|
||||
-- Add foreign key constraints
|
||||
ALTER TABLE chat_sessions
|
||||
ADD CONSTRAINT fk_chat_sessions_user_id
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE chat_messages_new
|
||||
ADD CONSTRAINT fk_chat_messages_new_session_id
|
||||
FOREIGN KEY (session_id) REFERENCES chat_sessions(session_id) ON DELETE CASCADE;
|
||||
|
||||
-- Insert sample data (optional)
|
||||
INSERT INTO chat_sessions (session_id, user_id, agent_id, title, message_count)
|
||||
VALUES ('sample-session-123', 1, 'd04bbced-ae93-4fb3-a015-9472e4e5e539', 'Sample Chat Session', 2)
|
||||
ON CONFLICT (session_id) DO NOTHING;
|
||||
|
||||
INSERT INTO chat_messages_new (session_id, message_type, content)
|
||||
VALUES
|
||||
('sample-session-123', 'user', 'Halo, ada yang bisa saya tanyakan?'),
|
||||
('sample-session-123', 'assistant', 'Halo! Tentu saja, saya siap membantu Anda dengan pertanyaan apapun.')
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
# Script to remove client_id from all entity files
|
||||
Write-Host "Starting client_id removal from entity files..." -ForegroundColor Green
|
||||
|
||||
# List of entity files that have client_id
|
||||
$entityFiles = @(
|
||||
"app/database/entity/users/users.entity.go",
|
||||
"app/database/entity/article_category_details/article_category_details.entity.go",
|
||||
"app/database/entity/advertisement.entity.go",
|
||||
"app/database/entity/activity_logs.entity.go",
|
||||
"app/database/entity/articles.entity.go",
|
||||
"app/database/entity/article_approvals.entity.go",
|
||||
"app/database/entity/article_comments.entity.go",
|
||||
"app/database/entity/audit_trails.entity.go",
|
||||
"app/database/entity/article_files.entity.go",
|
||||
"app/database/entity/article_categories.entity.go",
|
||||
"app/database/entity/csrf_token_records.entity.go",
|
||||
"app/database/entity/feedbacks.entity.go",
|
||||
"app/database/entity/forgot_passwords.entity.go",
|
||||
"app/database/entity/magazines.entity.go",
|
||||
"app/database/entity/master_modules.entity.go",
|
||||
"app/database/entity/one_time_passwords.entity.go",
|
||||
"app/database/entity/magazine_files.entity.go",
|
||||
"app/database/entity/master_menus.entity.go",
|
||||
"app/database/entity/user_roles.entity.go",
|
||||
"app/database/entity/subscription.entity.go",
|
||||
"app/database/entity/user_levels.entity.go",
|
||||
"app/database/entity/user_role_level_details.entity.go",
|
||||
"app/database/entity/user_role_accesses.entity.go"
|
||||
)
|
||||
|
||||
$processedFiles = 0
|
||||
$totalFiles = $entityFiles.Count
|
||||
|
||||
foreach ($filePath in $entityFiles) {
|
||||
if (Test-Path $filePath) {
|
||||
$processedFiles++
|
||||
Write-Progress -Activity "Processing entity files" -Status "Processing $([System.IO.Path]::GetFileName($filePath))" -PercentComplete (($processedFiles / $totalFiles) * 100)
|
||||
|
||||
Write-Host "Processing: $filePath" -ForegroundColor Yellow
|
||||
|
||||
$content = Get-Content $filePath -Raw
|
||||
|
||||
# Remove ClientId field definitions
|
||||
$content = $content -replace '\s*ClientId\s+\*uuid\.UUID\s+`json:"client_id"[^`]*`\s*\n', ''
|
||||
$content = $content -replace '\s*ClientId\s+\*uuid\.UUID\s+`json:"client_id"[^`]*`\s*', ''
|
||||
|
||||
# Remove ClientId field without json tag
|
||||
$content = $content -replace '\s*ClientId\s+\*uuid\.UUID\s*\n', ''
|
||||
$content = $content -replace '\s*ClientId\s+\*uuid\.UUID\s*', ''
|
||||
|
||||
# Remove uuid import if no more uuid usage
|
||||
if ($content -notmatch 'uuid\.') {
|
||||
$content = $content -replace '"github\.com/google/uuid"\s*\n', ''
|
||||
$content = $content -replace 'github\.com/google/uuid\s*\n', ''
|
||||
}
|
||||
|
||||
# Clean up extra empty lines
|
||||
$content = $content -replace '\n\s*\n\s*\n', "`n`n"
|
||||
|
||||
# Write back to file
|
||||
Set-Content -Path $filePath -Value $content -NoNewline
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Entity client_id removal process completed!" -ForegroundColor Green
|
||||
Write-Host "Processed $processedFiles entity files" -ForegroundColor Cyan
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
# Enhanced script to remove client_id from entity files
|
||||
Write-Host "Starting enhanced client_id removal from entity files..." -ForegroundColor Green
|
||||
|
||||
# List of entity files that have client_id
|
||||
$entityFiles = @(
|
||||
"app/database/entity/users/users.entity.go",
|
||||
"app/database/entity/article_category_details/article_category_details.entity.go",
|
||||
"app/database/entity/advertisement.entity.go",
|
||||
"app/database/entity/activity_logs.entity.go",
|
||||
"app/database/entity/articles.entity.go",
|
||||
"app/database/entity/article_approvals.entity.go",
|
||||
"app/database/entity/article_comments.entity.go",
|
||||
"app/database/entity/audit_trails.entity.go",
|
||||
"app/database/entity/article_files.entity.go",
|
||||
"app/database/entity/article_categories.entity.go",
|
||||
"app/database/entity/csrf_token_records.entity.go",
|
||||
"app/database/entity/feedbacks.entity.go",
|
||||
"app/database/entity/forgot_passwords.entity.go",
|
||||
"app/database/entity/magazines.entity.go",
|
||||
"app/database/entity/master_modules.entity.go",
|
||||
"app/database/entity/one_time_passwords.entity.go",
|
||||
"app/database/entity/magazine_files.entity.go",
|
||||
"app/database/entity/master_menus.entity.go",
|
||||
"app/database/entity/user_roles.entity.go",
|
||||
"app/database/entity/subscription.entity.go",
|
||||
"app/database/entity/user_levels.entity.go",
|
||||
"app/database/entity/user_role_level_details.entity.go",
|
||||
"app/database/entity/user_role_accesses.entity.go"
|
||||
)
|
||||
|
||||
$processedFiles = 0
|
||||
$totalFiles = $entityFiles.Count
|
||||
|
||||
foreach ($filePath in $entityFiles) {
|
||||
if (Test-Path $filePath) {
|
||||
$processedFiles++
|
||||
Write-Progress -Activity "Processing entity files" -Status "Processing $([System.IO.Path]::GetFileName($filePath))" -PercentComplete (($processedFiles / $totalFiles) * 100)
|
||||
|
||||
Write-Host "Processing: $filePath" -ForegroundColor Yellow
|
||||
|
||||
$content = Get-Content $filePath -Raw
|
||||
|
||||
# More specific patterns to remove ClientId field
|
||||
$content = $content -replace '\s*ClientId\s+\*uuid\.UUID\s+`json:"client_id"[^`]*`\s*\n', ''
|
||||
$content = $content -replace '\s*ClientId\s+\*uuid\.UUID\s+`json:"client_id"[^`]*`\s*', ''
|
||||
$content = $content -replace '\s*ClientId\s+\*uuid\.UUID\s*\n', ''
|
||||
$content = $content -replace '\s*ClientId\s+\*uuid\.UUID\s*', ''
|
||||
|
||||
# Remove ClientId field with different patterns
|
||||
$content = $content -replace '\s*ClientId\s+\*uuid\.UUID\s+`json:"client_id"[^`]*`\s*\n', ''
|
||||
$content = $content -replace '\s*ClientId\s+\*uuid\.UUID\s+`json:"client_id"[^`]*`\s*', ''
|
||||
|
||||
# Remove uuid import if no more uuid usage
|
||||
if ($content -notmatch 'uuid\.') {
|
||||
$content = $content -replace '"github\.com/google/uuid"\s*\n', ''
|
||||
$content = $content -replace 'github\.com/google/uuid\s*\n', ''
|
||||
}
|
||||
|
||||
# Clean up extra empty lines
|
||||
$content = $content -replace '\n\s*\n\s*\n', "`n`n"
|
||||
|
||||
# Write back to file
|
||||
Set-Content -Path $filePath -Value $content -NoNewline
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Enhanced entity client_id removal process completed!" -ForegroundColor Green
|
||||
Write-Host "Processed $processedFiles entity files" -ForegroundColor Cyan
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
# Final script to remove ClientId from all entity files
|
||||
Write-Host "Final removal of ClientId from entity files..." -ForegroundColor Green
|
||||
|
||||
# Get all .go files in entity directory
|
||||
$entityFiles = Get-ChildItem -Path "app/database/entity" -Recurse -Filter "*.go"
|
||||
|
||||
foreach ($file in $entityFiles) {
|
||||
$content = Get-Content $file.FullName -Raw
|
||||
|
||||
# Skip if file doesn't contain ClientId
|
||||
if ($content -notmatch "ClientId") {
|
||||
continue
|
||||
}
|
||||
|
||||
Write-Host "Processing: $($file.Name)" -ForegroundColor Yellow
|
||||
|
||||
# Remove ClientId field with regex
|
||||
$content = $content -replace '\s*ClientId\s+\*uuid\.UUID\s+`json:"client_id"[^`]*`\s*\n', ''
|
||||
$content = $content -replace '\s*ClientId\s+\*uuid\.UUID\s+`json:"client_id"[^`]*`\s*', ''
|
||||
$content = $content -replace '\s*ClientId\s+\*uuid\.UUID\s*\n', ''
|
||||
$content = $content -replace '\s*ClientId\s+\*uuid\.UUID\s*', ''
|
||||
|
||||
# Remove uuid import if no more uuid usage
|
||||
if ($content -notmatch 'uuid\.') {
|
||||
$content = $content -replace '"github\.com/google/uuid"\s*\n', ''
|
||||
$content = $content -replace 'github\.com/google/uuid\s*\n', ''
|
||||
}
|
||||
|
||||
# Write back to file
|
||||
Set-Content -Path $file.FullName -Value $content -NoNewline
|
||||
}
|
||||
|
||||
Write-Host "Final ClientId removal completed!" -ForegroundColor Green
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
# Enhanced script to remove client_id usage from remaining files
|
||||
Write-Host "Starting enhanced client_id removal process..." -ForegroundColor Green
|
||||
|
||||
# List of files that still have client_id usage
|
||||
$problemFiles = @(
|
||||
"app/module/custom_static_pages/repository/custom_static_pages.repository.go",
|
||||
"app/module/article_files/repository/article_files.repository.go",
|
||||
"app/module/article_comments/repository/article_comments.repository.go",
|
||||
"app/module/article_categories/repository/article_categories.repository.go",
|
||||
"app/module/articles/repository/articles.repository.go",
|
||||
"app/module/advertisement/repository/advertisement.repository.go",
|
||||
"app/module/activity_logs/repository/activity_logs.repository.go",
|
||||
"app/module/users/service/users.service.go",
|
||||
"app/module/subscription/repository/subscription.repository.go",
|
||||
"app/module/subscription/controller/subscription.controller.go",
|
||||
"app/module/subscription/service/subscription.service.go",
|
||||
"app/module/magazines/controller/magazines.controller.go",
|
||||
"app/module/magazines/repository/magazines.repository.go",
|
||||
"app/module/magazines/service/magazines.service.go",
|
||||
"app/module/users/controller/users.controller.go",
|
||||
"app/module/feedbacks/service/feedbacks.service.go",
|
||||
"app/module/feedbacks/repository/feedbacks.repository.go",
|
||||
"app/module/feedbacks/controller/feedbacks.controller.go"
|
||||
)
|
||||
|
||||
foreach ($filePath in $problemFiles) {
|
||||
if (Test-Path $filePath) {
|
||||
Write-Host "Processing: $filePath" -ForegroundColor Yellow
|
||||
|
||||
$content = Get-Content $filePath -Raw
|
||||
|
||||
# More aggressive replacements
|
||||
$content = $content -replace 'clientId \*uuid\.UUID,?\s*', ''
|
||||
$content = $content -replace 'clientId \*uuid\.UUID\s*,?\s*', ''
|
||||
$content = $content -replace 'clientId \*uuid\.UUID\s*', ''
|
||||
|
||||
# Remove clientId assignments
|
||||
$content = $content -replace 'clientId := middleware\.GetClientID\(c\)\s*\n\s*', ''
|
||||
$content = $content -replace 'clientId := middleware\.GetClientID\(c\)', ''
|
||||
|
||||
# Remove clientId from function calls - be more specific
|
||||
$content = $content -replace '\(clientId,', '('
|
||||
$content = $content -replace ', clientId\)', ')'
|
||||
$content = $content -replace '\(clientId\)', '()'
|
||||
|
||||
# Remove client_id filters
|
||||
$content = $content -replace 'if clientId != nil \{\s*\n\s*query = query\.Where\("client_id = \?", clientId\)\s*\n\s*\}', ''
|
||||
$content = $content -replace 'if clientId != nil \{\s*\n\s*query\.Where\("client_id = \?", clientId\)\s*\n\s*\}', ''
|
||||
|
||||
# Remove clientId logging
|
||||
$content = $content -replace '_i\.Log\.Info\(\)\.Interface\("clientId", clientId\)\.Msg\(""\)\s*\n\s*', ''
|
||||
|
||||
# Remove clientId comments
|
||||
$content = $content -replace '// Add ClientId filter\s*\n', ''
|
||||
$content = $content -replace '// Get ClientId from context\s*\n', ''
|
||||
|
||||
# Clean up function signatures
|
||||
$content = $content -replace ',\s*,', ','
|
||||
$content = $content -replace '\(\s*,', '('
|
||||
$content = $content -replace ',\s*\)', ')'
|
||||
$content = $content -replace '\(\s*\)', '()'
|
||||
|
||||
# Remove unused imports
|
||||
if ($content -notmatch 'uuid\.') {
|
||||
$content = $content -replace '"github\.com/google/uuid"\s*\n', ''
|
||||
$content = $content -replace 'github\.com/google/uuid\s*\n', ''
|
||||
}
|
||||
|
||||
if ($content -notmatch 'middleware\.') {
|
||||
$content = $content -replace '"narasi-ahli-be/app/middleware"\s*\n', ''
|
||||
$content = $content -replace 'narasi-ahli-be/app/middleware\s*\n', ''
|
||||
}
|
||||
|
||||
# Write back to file
|
||||
Set-Content -Path $filePath -Value $content -NoNewline
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "Enhanced client ID removal process completed!" -ForegroundColor Green
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"narasi-ahli-be/app/database"
|
||||
"narasi-ahli-be/app/database/entity"
|
||||
"narasi-ahli-be/migrations"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fx.New(
|
||||
fx.Provide(database.NewDatabase),
|
||||
fx.Provide(func() zerolog.Logger {
|
||||
return zerolog.Nop()
|
||||
}),
|
||||
fx.Invoke(func(db *database.Database) {
|
||||
// Run migration
|
||||
err := migrations.CreateChatHistoryTables(db)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Also auto-migrate existing entities
|
||||
err = db.DB.AutoMigrate(
|
||||
&entity.AIChatSessions{},
|
||||
&entity.AIChatMessages{},
|
||||
&entity.AIChatLogs{},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
println("Migration completed successfully!")
|
||||
}),
|
||||
).Run()
|
||||
}
|
||||
Loading…
Reference in New Issue