feat: update ai-chat an chat_history
This commit is contained in:
parent
a88bd957e3
commit
f1b49d0c63
|
|
@ -30,4 +30,4 @@ deploy:
|
||||||
services:
|
services:
|
||||||
- docker:dind
|
- docker:dind
|
||||||
script:
|
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/controller"
|
||||||
"narasi-ahli-be/app/module/ai_chat/repository"
|
"narasi-ahli-be/app/module/ai_chat/repository"
|
||||||
"narasi-ahli-be/app/module/ai_chat/service"
|
"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"
|
"go.uber.org/fx"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Module = fx.Options(
|
// struct of AIChatRouter
|
||||||
fx.Provide(
|
type AIChatRouter struct {
|
||||||
repository.NewAIChatRepository,
|
App fiber.Router
|
||||||
service.NewAIChatService,
|
Controller controller.AIChatController
|
||||||
controller.NewAIChatController,
|
}
|
||||||
),
|
|
||||||
fx.Invoke(func(
|
// register bulky of AI Chat module
|
||||||
aiChatController controller.AIChatController,
|
var NewAIChatModule = fx.Options(
|
||||||
usersRepo usersRepository.UsersRepository,
|
// register repository of AI Chat module
|
||||||
log zerolog.Logger,
|
fx.Provide(repository.NewAIChatRepository),
|
||||||
) {
|
|
||||||
log.Info().Msg("AI Chat module initialized successfully")
|
// 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 (
|
import (
|
||||||
"narasi-ahli-be/app/module/activity_logs"
|
"narasi-ahli-be/app/module/activity_logs"
|
||||||
"narasi-ahli-be/app/module/advertisement"
|
"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_approvals"
|
||||||
"narasi-ahli-be/app/module/article_categories"
|
"narasi-ahli-be/app/module/article_categories"
|
||||||
"narasi-ahli-be/app/module/article_category_details"
|
"narasi-ahli-be/app/module/article_category_details"
|
||||||
"narasi-ahli-be/app/module/article_comments"
|
"narasi-ahli-be/app/module/article_comments"
|
||||||
"narasi-ahli-be/app/module/article_files"
|
"narasi-ahli-be/app/module/article_files"
|
||||||
"narasi-ahli-be/app/module/articles"
|
"narasi-ahli-be/app/module/articles"
|
||||||
|
"narasi-ahli-be/app/module/chat_history"
|
||||||
"narasi-ahli-be/app/module/cities"
|
"narasi-ahli-be/app/module/cities"
|
||||||
"narasi-ahli-be/app/module/custom_static_pages"
|
"narasi-ahli-be/app/module/custom_static_pages"
|
||||||
"narasi-ahli-be/app/module/districts"
|
"narasi-ahli-be/app/module/districts"
|
||||||
|
|
@ -39,12 +41,14 @@ type Router struct {
|
||||||
|
|
||||||
ActivityLogsRouter *activity_logs.ActivityLogsRouter
|
ActivityLogsRouter *activity_logs.ActivityLogsRouter
|
||||||
AdvertisementRouter *advertisement.AdvertisementRouter
|
AdvertisementRouter *advertisement.AdvertisementRouter
|
||||||
|
AIChatRouter *ai_chat.AIChatRouter
|
||||||
ArticleCategoriesRouter *article_categories.ArticleCategoriesRouter
|
ArticleCategoriesRouter *article_categories.ArticleCategoriesRouter
|
||||||
ArticleCategoryDetailsRouter *article_category_details.ArticleCategoryDetailsRouter
|
ArticleCategoryDetailsRouter *article_category_details.ArticleCategoryDetailsRouter
|
||||||
ArticleFilesRouter *article_files.ArticleFilesRouter
|
ArticleFilesRouter *article_files.ArticleFilesRouter
|
||||||
ArticleCommentsRouter *article_comments.ArticleCommentsRouter
|
ArticleCommentsRouter *article_comments.ArticleCommentsRouter
|
||||||
ArticleApprovalsRouter *article_approvals.ArticleApprovalsRouter
|
ArticleApprovalsRouter *article_approvals.ArticleApprovalsRouter
|
||||||
ArticlesRouter *articles.ArticlesRouter
|
ArticlesRouter *articles.ArticlesRouter
|
||||||
|
ChatHistoryRouter *chat_history.ChatHistoryRouter
|
||||||
CitiesRouter *cities.CitiesRouter
|
CitiesRouter *cities.CitiesRouter
|
||||||
CustomStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter
|
CustomStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter
|
||||||
DistrictsRouter *districts.DistrictsRouter
|
DistrictsRouter *districts.DistrictsRouter
|
||||||
|
|
@ -70,12 +74,14 @@ func NewRouter(
|
||||||
|
|
||||||
activityLogsRouter *activity_logs.ActivityLogsRouter,
|
activityLogsRouter *activity_logs.ActivityLogsRouter,
|
||||||
advertisementRouter *advertisement.AdvertisementRouter,
|
advertisementRouter *advertisement.AdvertisementRouter,
|
||||||
|
aiChatRouter *ai_chat.AIChatRouter,
|
||||||
articleCategoriesRouter *article_categories.ArticleCategoriesRouter,
|
articleCategoriesRouter *article_categories.ArticleCategoriesRouter,
|
||||||
articleCategoryDetailsRouter *article_category_details.ArticleCategoryDetailsRouter,
|
articleCategoryDetailsRouter *article_category_details.ArticleCategoryDetailsRouter,
|
||||||
articleFilesRouter *article_files.ArticleFilesRouter,
|
articleFilesRouter *article_files.ArticleFilesRouter,
|
||||||
articleCommentsRouter *article_comments.ArticleCommentsRouter,
|
articleCommentsRouter *article_comments.ArticleCommentsRouter,
|
||||||
articleApprovalsRouter *article_approvals.ArticleApprovalsRouter,
|
articleApprovalsRouter *article_approvals.ArticleApprovalsRouter,
|
||||||
articlesRouter *articles.ArticlesRouter,
|
articlesRouter *articles.ArticlesRouter,
|
||||||
|
chatHistoryRouter *chat_history.ChatHistoryRouter,
|
||||||
citiesRouter *cities.CitiesRouter,
|
citiesRouter *cities.CitiesRouter,
|
||||||
customStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter,
|
customStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter,
|
||||||
districtsRouter *districts.DistrictsRouter,
|
districtsRouter *districts.DistrictsRouter,
|
||||||
|
|
@ -99,12 +105,14 @@ func NewRouter(
|
||||||
Cfg: cfg,
|
Cfg: cfg,
|
||||||
ActivityLogsRouter: activityLogsRouter,
|
ActivityLogsRouter: activityLogsRouter,
|
||||||
AdvertisementRouter: advertisementRouter,
|
AdvertisementRouter: advertisementRouter,
|
||||||
|
AIChatRouter: aiChatRouter,
|
||||||
ArticleCategoriesRouter: articleCategoriesRouter,
|
ArticleCategoriesRouter: articleCategoriesRouter,
|
||||||
ArticleCategoryDetailsRouter: articleCategoryDetailsRouter,
|
ArticleCategoryDetailsRouter: articleCategoryDetailsRouter,
|
||||||
ArticleFilesRouter: articleFilesRouter,
|
ArticleFilesRouter: articleFilesRouter,
|
||||||
ArticleCommentsRouter: articleCommentsRouter,
|
ArticleCommentsRouter: articleCommentsRouter,
|
||||||
ArticleApprovalsRouter: articleApprovalsRouter,
|
ArticleApprovalsRouter: articleApprovalsRouter,
|
||||||
ArticlesRouter: articlesRouter,
|
ArticlesRouter: articlesRouter,
|
||||||
|
ChatHistoryRouter: chatHistoryRouter,
|
||||||
CitiesRouter: citiesRouter,
|
CitiesRouter: citiesRouter,
|
||||||
CustomStaticPagesRouter: customStaticPagesRouter,
|
CustomStaticPagesRouter: customStaticPagesRouter,
|
||||||
DistrictsRouter: districtsRouter,
|
DistrictsRouter: districtsRouter,
|
||||||
|
|
@ -138,12 +146,14 @@ func (r *Router) Register() {
|
||||||
// Register routes of modules
|
// Register routes of modules
|
||||||
r.ActivityLogsRouter.RegisterActivityLogsRoutes()
|
r.ActivityLogsRouter.RegisterActivityLogsRoutes()
|
||||||
r.AdvertisementRouter.RegisterAdvertisementRoutes()
|
r.AdvertisementRouter.RegisterAdvertisementRoutes()
|
||||||
|
r.AIChatRouter.RegisterAIChatRoutes()
|
||||||
r.ArticleCategoriesRouter.RegisterArticleCategoriesRoutes()
|
r.ArticleCategoriesRouter.RegisterArticleCategoriesRoutes()
|
||||||
r.ArticleCategoryDetailsRouter.RegisterArticleCategoryDetailsRoutes()
|
r.ArticleCategoryDetailsRouter.RegisterArticleCategoryDetailsRoutes()
|
||||||
r.ArticleFilesRouter.RegisterArticleFilesRoutes()
|
r.ArticleFilesRouter.RegisterArticleFilesRoutes()
|
||||||
r.ArticleApprovalsRouter.RegisterArticleApprovalsRoutes()
|
r.ArticleApprovalsRouter.RegisterArticleApprovalsRoutes()
|
||||||
r.ArticlesRouter.RegisterArticlesRoutes()
|
r.ArticlesRouter.RegisterArticlesRoutes()
|
||||||
r.ArticleCommentsRouter.RegisterArticleCommentsRoutes()
|
r.ArticleCommentsRouter.RegisterArticleCommentsRoutes()
|
||||||
|
r.ChatHistoryRouter.RegisterChatHistoryRoutes()
|
||||||
r.CitiesRouter.RegisterCitiesRoutes()
|
r.CitiesRouter.RegisterCitiesRoutes()
|
||||||
r.CustomStaticPagesRouter.RegisterCustomStaticPagesRoutes()
|
r.CustomStaticPagesRouter.RegisterCustomStaticPagesRoutes()
|
||||||
r.DistrictsRouter.RegisterDistrictsRoutes()
|
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/middleware"
|
||||||
"narasi-ahli-be/app/module/activity_logs"
|
"narasi-ahli-be/app/module/activity_logs"
|
||||||
"narasi-ahli-be/app/module/advertisement"
|
"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_approvals"
|
||||||
"narasi-ahli-be/app/module/article_categories"
|
"narasi-ahli-be/app/module/article_categories"
|
||||||
"narasi-ahli-be/app/module/article_category_details"
|
"narasi-ahli-be/app/module/article_category_details"
|
||||||
"narasi-ahli-be/app/module/article_comments"
|
"narasi-ahli-be/app/module/article_comments"
|
||||||
"narasi-ahli-be/app/module/article_files"
|
"narasi-ahli-be/app/module/article_files"
|
||||||
"narasi-ahli-be/app/module/articles"
|
"narasi-ahli-be/app/module/articles"
|
||||||
|
"narasi-ahli-be/app/module/chat_history"
|
||||||
"narasi-ahli-be/app/module/cities"
|
"narasi-ahli-be/app/module/cities"
|
||||||
"narasi-ahli-be/app/module/custom_static_pages"
|
"narasi-ahli-be/app/module/custom_static_pages"
|
||||||
"narasi-ahli-be/app/module/districts"
|
"narasi-ahli-be/app/module/districts"
|
||||||
|
|
@ -65,12 +67,14 @@ func main() {
|
||||||
// provide modules
|
// provide modules
|
||||||
activity_logs.NewActivityLogsModule,
|
activity_logs.NewActivityLogsModule,
|
||||||
advertisement.NewAdvertisementModule,
|
advertisement.NewAdvertisementModule,
|
||||||
|
ai_chat.NewAIChatModule,
|
||||||
article_categories.NewArticleCategoriesModule,
|
article_categories.NewArticleCategoriesModule,
|
||||||
article_category_details.NewArticleCategoryDetailsModule,
|
article_category_details.NewArticleCategoryDetailsModule,
|
||||||
article_files.NewArticleFilesModule,
|
article_files.NewArticleFilesModule,
|
||||||
article_approvals.NewArticleApprovalsModule,
|
article_approvals.NewArticleApprovalsModule,
|
||||||
articles.NewArticlesModule,
|
articles.NewArticlesModule,
|
||||||
article_comments.NewArticleCommentsModule,
|
article_comments.NewArticleCommentsModule,
|
||||||
|
chat_history.NewChatHistoryModule,
|
||||||
cities.NewCitiesModule,
|
cities.NewCitiesModule,
|
||||||
custom_static_pages.NewCustomStaticPagesModule,
|
custom_static_pages.NewCustomStaticPagesModule,
|
||||||
districts.NewDistrictsModule,
|
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