feat: add chats features

This commit is contained in:
hanif salafi 2025-09-22 20:05:40 +07:00
parent 86ea5f66d1
commit 9659007a06
24 changed files with 4029 additions and 356 deletions

View File

@ -1,206 +0,0 @@
# 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.

View File

@ -6,18 +6,19 @@ import (
)
type ChatMessages struct {
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
ConversationID uint `json:"conversation_id" gorm:"type:int4;not null;index"`
SenderID uint `json:"sender_id" gorm:"type:int4;not null;index"`
MessageText *string `json:"message_text" gorm:"type:text"`
MessageType string `json:"message_type" gorm:"type:varchar;not null;default:'text'"`
FileURL *string `json:"file_url" gorm:"type:varchar"`
FileName *string `json:"file_name" gorm:"type:varchar"`
FileSize *int64 `json:"file_size" gorm:"type:bigint"`
IsRead bool `json:"is_read" gorm:"default:false"`
IsActive bool `json:"is_active" gorm:"type:bool;default:true"`
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
// Conversation *Conversations `json:"conversation" gorm:"foreignKey:ConversationID;references:ID"`
Sender *users.Users `json:"sender" gorm:"foreignKey:SenderID;references:ID"`
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
ChatSessionID uint `json:"chat_session_id" gorm:"type:int4;not null;index"`
SenderID uint `json:"sender_id" gorm:"type:int4;not null;index"`
Message string `json:"message" gorm:"type:text;not null"`
MessageType string `json:"message_type" gorm:"type:varchar(20);not null;default:'text';check:message_type IN ('text', 'image', 'file', 'user', 'assistant')"` // 'text', 'image', 'file', 'user', 'assistant'
IsEdited bool `json:"is_edited" gorm:"default:false"`
EditedAt *time.Time `json:"edited_at"`
IsDeleted bool `json:"is_deleted" gorm:"default:false"`
DeletedAt *time.Time `json:"deleted_at"`
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
// Relationships
ChatSession *ChatSessions `json:"chat_session" gorm:"foreignKey:ChatSessionID;references:ID"`
Sender *users.Users `json:"sender" gorm:"foreignKey:SenderID;references:ID"`
}

View File

@ -1,14 +0,0 @@
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"`
}

View File

@ -0,0 +1,21 @@
package entity
import (
"narasi-ahli-be/app/database/entity/users"
"time"
)
type ChatParticipants struct {
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
ChatSessionID uint `json:"chat_session_id" gorm:"type:int4;not null;index"`
UserID uint `json:"user_id" gorm:"type:int4;not null;index"`
JoinedAt time.Time `json:"joined_at" gorm:"default:now()"`
LeftAt *time.Time `json:"left_at"`
IsActive bool `json:"is_active" gorm:"default:true"`
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
// Relationships
ChatSession *ChatSessions `json:"chat_session" gorm:"foreignKey:ChatSessionID;references:ID"`
User *users.Users `json:"user" gorm:"foreignKey:UserID;references:ID"`
}

View File

@ -6,14 +6,15 @@ import (
)
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"`
IsActive bool `json:"is_active" gorm:"type:bool;default:true"`
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"`
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
Name *string `json:"name" gorm:"type:varchar(255)"` // null for personal chat, filled for group chat
Type string `json:"type" gorm:"type:varchar(20);not null;default:'personal'"` // 'personal' or 'group'
CreatedBy uint `json:"created_by" gorm:"type:int4;not null;index"`
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
// Relationships
Creator *users.Users `json:"creator" gorm:"foreignKey:CreatedBy;references:ID"`
Participants []*ChatParticipants `json:"participants" gorm:"foreignKey:ChatSessionID;references:ID"`
Messages []*ChatMessages `json:"messages" gorm:"foreignKey:ChatSessionID;references:ID"`
}

View File

@ -122,6 +122,8 @@ func Models() []interface{} {
entity.ResearchJournals{},
entity.Conversations{},
entity.ChatMessages{},
entity.ChatParticipants{},
entity.ChatSessions{},
entity.AIChatSessions{},
entity.AIChatMessages{},
entity.AIChatLogs{},

View File

@ -0,0 +1,70 @@
package chat
import (
"narasi-ahli-be/app/module/chat/controller"
"narasi-ahli-be/app/module/chat/repository"
"narasi-ahli-be/app/module/chat/service"
"github.com/gofiber/fiber/v2"
"go.uber.org/fx"
)
// struct of ChatRouter
type ChatRouter struct {
App fiber.Router
Controller *controller.Controller
}
// register bulky of Chat module
var NewChatModule = fx.Options(
// register repository of Chat module
fx.Provide(repository.NewChatRepository),
// register service of Chat module
fx.Provide(service.NewChatService),
// register controller of Chat module
fx.Provide(controller.NewController),
// register router of Chat module
fx.Provide(NewChatRouter),
)
// init ChatRouter
func NewChatRouter(fiber *fiber.App, controller *controller.Controller) *ChatRouter {
return &ChatRouter{
App: fiber,
Controller: controller,
}
}
// register routes of Chat module
func (_i *ChatRouter) RegisterChatRoutes() {
// define controllers
chatController := _i.Controller.Chat
// define routes
_i.App.Route("/chat", func(router fiber.Router) {
// Chat Session routes
router.Route("/sessions", func(sessionRouter fiber.Router) {
sessionRouter.Get("/", chatController.GetAllChatSessions)
sessionRouter.Get("/:id", chatController.GetChatSessionByID)
sessionRouter.Post("/", chatController.CreateChatSession)
sessionRouter.Put("/:id", chatController.UpdateChatSession)
sessionRouter.Delete("/:id", chatController.DeleteChatSession)
// Chat Participant routes
sessionRouter.Post("/:chatSessionId/participants", chatController.AddParticipantToChat)
sessionRouter.Delete("/:chatSessionId/participants", chatController.RemoveParticipantFromChat)
})
// Chat Message routes
router.Route("/messages", func(messageRouter fiber.Router) {
messageRouter.Get("/", chatController.GetAllChatMessages)
messageRouter.Get("/:id", chatController.GetChatMessageByID)
messageRouter.Post("/", chatController.CreateChatMessage)
messageRouter.Put("/:id", chatController.UpdateChatMessage)
messageRouter.Delete("/:id", chatController.DeleteChatMessage)
})
})
}

View File

@ -0,0 +1,474 @@
package controller
import (
"narasi-ahli-be/app/module/chat/request"
"narasi-ahli-be/app/module/chat/service"
"narasi-ahli-be/utils/paginator"
utilRes "narasi-ahli-be/utils/response"
utilVal "narasi-ahli-be/utils/validator"
"strconv"
"github.com/gofiber/fiber/v2"
)
type chatController struct {
chatService service.ChatService
}
type ChatController interface {
// Chat Session endpoints
GetAllChatSessions(c *fiber.Ctx) error
GetChatSessionByID(c *fiber.Ctx) error
CreateChatSession(c *fiber.Ctx) error
UpdateChatSession(c *fiber.Ctx) error
DeleteChatSession(c *fiber.Ctx) error
// Chat Message endpoints
GetAllChatMessages(c *fiber.Ctx) error
GetChatMessageByID(c *fiber.Ctx) error
CreateChatMessage(c *fiber.Ctx) error
UpdateChatMessage(c *fiber.Ctx) error
DeleteChatMessage(c *fiber.Ctx) error
// Chat Participant endpoints
AddParticipantToChat(c *fiber.Ctx) error
RemoveParticipantFromChat(c *fiber.Ctx) error
}
func NewChatController(chatService service.ChatService) ChatController {
return &chatController{
chatService: chatService,
}
}
// GetAllChatSessions - Get all chat sessions for a user
// @Summary Get all chat sessions
// @Description API for getting all chat sessions for authenticated user
// @Tags Chat
// @Security Bearer
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param type query string false "Chat type (personal or group)"
// @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/sessions [get]
func (_i *chatController) GetAllChatSessions(c *fiber.Ctx) error {
paginate, err := paginator.Paginate(c)
if err != nil {
return err
}
authToken := c.Get("Authorization")
reqContext := request.ChatSessionQueryRequestContext{
Type: c.Query("type"),
}
req := reqContext.ToParamRequest()
req.Pagination = paginate
chatSessions, paging, err := _i.chatService.GetAllChatSessions(authToken, req)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat sessions successfully retrieved"},
Data: chatSessions,
Meta: paging,
})
}
// GetChatSessionByID - Get one chat session
// @Summary Get one chat session
// @Description API for getting one chat session
// @Tags Chat
// @Security Bearer
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param id path int true "Chat Session ID"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/sessions/{id} [get]
func (_i *chatController) GetChatSessionByID(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
if err != nil {
return err
}
authToken := c.Get("Authorization")
chatSession, err := _i.chatService.GetChatSessionByID(authToken, uint(id))
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat session successfully retrieved"},
Data: chatSession,
})
}
// CreateChatSession - Create chat session
// @Summary Create chat session
// @Description API for creating a new chat session (personal or group)
// @Tags Chat
// @Security Bearer
// @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.ChatSessionCreateRequest 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/sessions [post]
func (_i *chatController) CreateChatSession(c *fiber.Ctx) error {
authToken := c.Get("Authorization")
req := new(request.ChatSessionCreateRequest)
if err := utilVal.ParseAndValidate(c, req); err != nil {
return err
}
dataResult, err := _i.chatService.CreateChatSession(authToken, *req)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat session successfully created"},
Data: dataResult,
})
}
// UpdateChatSession - Update chat session
// @Summary Update chat session
// @Description API for updating chat session (only creator can update)
// @Tags Chat
// @Security Bearer
// @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 id path int true "Chat Session ID"
// @Param payload body request.ChatSessionUpdateRequest 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/sessions/{id} [put]
func (_i *chatController) UpdateChatSession(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
if err != nil {
return err
}
authToken := c.Get("Authorization")
req := new(request.ChatSessionUpdateRequest)
if err := utilVal.ParseAndValidate(c, req); err != nil {
return err
}
err = _i.chatService.UpdateChatSession(authToken, uint(id), *req)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat session successfully updated"},
})
}
// DeleteChatSession - Delete chat session
// @Summary Delete chat session
// @Description API for deleting chat session (only creator can delete)
// @Tags Chat
// @Security Bearer
// @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 id path int true "Chat Session ID"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/sessions/{id} [delete]
func (_i *chatController) DeleteChatSession(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
if err != nil {
return err
}
authToken := c.Get("Authorization")
err = _i.chatService.DeleteChatSession(authToken, uint(id))
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat session successfully deleted"},
})
}
// GetAllChatMessages - Get all messages in a chat session
// @Summary Get all chat messages
// @Description API for getting all messages in a specific chat session
// @Tags Chat
// @Security Bearer
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param chatSessionId query uint true "Chat Session ID"
// @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/messages [get]
func (_i *chatController) GetAllChatMessages(c *fiber.Ctx) error {
paginate, err := paginator.Paginate(c)
if err != nil {
return err
}
authToken := c.Get("Authorization")
reqContext := request.ChatMessageQueryRequestContext{
ChatSessionID: c.Query("chatSessionId"),
}
req := reqContext.ToParamRequest()
req.Pagination = paginate
chatMessages, paging, err := _i.chatService.GetAllChatMessages(authToken, req)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat messages successfully retrieved"},
Data: chatMessages,
Meta: paging,
})
}
// GetChatMessageByID - Get one chat message
// @Summary Get one chat message
// @Description API for getting one chat message
// @Tags Chat
// @Security Bearer
// @Param Authorization header string false "Insert your access token" default(Bearer <Add access token here>)
// @Param id path int true "Chat Message ID"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/messages/{id} [get]
func (_i *chatController) GetChatMessageByID(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
if err != nil {
return err
}
authToken := c.Get("Authorization")
chatMessage, err := _i.chatService.GetChatMessageByID(authToken, uint(id))
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat message successfully retrieved"},
Data: chatMessage,
})
}
// CreateChatMessage - Create chat message
// @Summary Create chat message
// @Description API for creating a new chat message
// @Tags Chat
// @Security Bearer
// @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.ChatMessageCreateRequest 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/messages [post]
func (_i *chatController) CreateChatMessage(c *fiber.Ctx) error {
authToken := c.Get("Authorization")
req := new(request.ChatMessageCreateRequest)
if err := utilVal.ParseAndValidate(c, req); err != nil {
return err
}
dataResult, err := _i.chatService.CreateChatMessage(authToken, *req)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat message successfully created"},
Data: dataResult,
})
}
// UpdateChatMessage - Update chat message
// @Summary Update chat message
// @Description API for updating chat message (only sender can update)
// @Tags Chat
// @Security Bearer
// @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 id path int true "Chat Message ID"
// @Param payload body request.ChatMessageUpdateRequest 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/messages/{id} [put]
func (_i *chatController) UpdateChatMessage(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
if err != nil {
return err
}
authToken := c.Get("Authorization")
req := new(request.ChatMessageUpdateRequest)
if err := utilVal.ParseAndValidate(c, req); err != nil {
return err
}
err = _i.chatService.UpdateChatMessage(authToken, uint(id), *req)
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat message successfully updated"},
})
}
// DeleteChatMessage - Delete chat message
// @Summary Delete chat message
// @Description API for deleting chat message (only sender can delete)
// @Tags Chat
// @Security Bearer
// @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 id path int true "Chat Message ID"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/messages/{id} [delete]
func (_i *chatController) DeleteChatMessage(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 0)
if err != nil {
return err
}
authToken := c.Get("Authorization")
err = _i.chatService.DeleteChatMessage(authToken, uint(id))
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Chat message successfully deleted"},
})
}
// AddParticipantToChat - Add participant to chat session
// @Summary Add participant to chat session
// @Description API for adding a participant to a chat session (only creator can add)
// @Tags Chat
// @Security Bearer
// @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 chatSessionId path int true "Chat Session ID"
// @Param participantUserId query uint true "Participant User ID"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/sessions/{chatSessionId}/participants [post]
func (_i *chatController) AddParticipantToChat(c *fiber.Ctx) error {
chatSessionId, err := strconv.ParseUint(c.Params("chatSessionId"), 10, 0)
if err != nil {
return err
}
authToken := c.Get("Authorization")
participantUserId, err := strconv.ParseUint(c.Query("participantUserId"), 10, 0)
if err != nil {
return utilRes.Resp(c, utilRes.Response{
Success: false,
Messages: utilRes.Messages{"participantUserId parameter is required and must be a valid number"},
})
}
err = _i.chatService.AddParticipantToChat(authToken, uint(chatSessionId), uint(participantUserId))
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Participant successfully added to chat session"},
})
}
// RemoveParticipantFromChat - Remove participant from chat session
// @Summary Remove participant from chat session
// @Description API for removing a participant from a chat session (creator can remove anyone, user can remove themselves)
// @Tags Chat
// @Security Bearer
// @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 chatSessionId path int true "Chat Session ID"
// @Param participantUserId query uint true "Participant User ID"
// @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /chat/sessions/{chatSessionId}/participants [delete]
func (_i *chatController) RemoveParticipantFromChat(c *fiber.Ctx) error {
chatSessionId, err := strconv.ParseUint(c.Params("chatSessionId"), 10, 0)
if err != nil {
return err
}
authToken := c.Get("Authorization")
participantUserId, err := strconv.ParseUint(c.Query("participantUserId"), 10, 0)
if err != nil {
return utilRes.Resp(c, utilRes.Response{
Success: false,
Messages: utilRes.Messages{"participantUserId parameter is required and must be a valid number"},
})
}
err = _i.chatService.RemoveParticipantFromChat(authToken, uint(chatSessionId), uint(participantUserId))
if err != nil {
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"Participant successfully removed from chat session"},
})
}

View File

@ -0,0 +1,13 @@
package controller
import "narasi-ahli-be/app/module/chat/service"
type Controller struct {
Chat ChatController
}
func NewController(ChatService service.ChatService) *Controller {
return &Controller{
Chat: NewChatController(ChatService),
}
}

View File

@ -0,0 +1,105 @@
package mapper
import (
"narasi-ahli-be/app/database/entity"
"narasi-ahli-be/app/module/chat/response"
)
// Chat Session Mapper
func ChatSessionResponseMapper(chatSession *entity.ChatSessions) *response.ChatSessionResponse {
result := &response.ChatSessionResponse{
ID: chatSession.ID,
Name: chatSession.Name,
Type: chatSession.Type,
CreatedBy: chatSession.CreatedBy,
CreatedAt: chatSession.CreatedAt,
UpdatedAt: chatSession.UpdatedAt,
}
if chatSession.Creator != nil {
result.Creator = &response.UserBasicInfo{
ID: chatSession.Creator.ID,
Username: chatSession.Creator.Username,
Fullname: chatSession.Creator.Fullname,
Email: chatSession.Creator.Email,
}
}
// Map participants
if len(chatSession.Participants) > 0 {
for _, participant := range chatSession.Participants {
if participant.IsActive {
result.Participants = append(result.Participants, ChatParticipantResponseMapper(participant))
}
}
}
// Map last message
if len(chatSession.Messages) > 0 {
// Find the latest message that is not deleted
var lastMessage *entity.ChatMessages
for _, message := range chatSession.Messages {
if !message.IsDeleted && (lastMessage == nil || message.CreatedAt.After(lastMessage.CreatedAt)) {
lastMessage = message
}
}
if lastMessage != nil {
result.LastMessage = ChatMessageResponseMapper(lastMessage)
}
}
return result
}
// Chat Message Mapper
func ChatMessageResponseMapper(chatMessage *entity.ChatMessages) *response.ChatMessageResponse {
result := &response.ChatMessageResponse{
ID: chatMessage.ID,
ChatSessionID: chatMessage.ChatSessionID,
SenderID: chatMessage.SenderID,
Message: chatMessage.Message,
MessageType: chatMessage.MessageType,
IsEdited: chatMessage.IsEdited,
EditedAt: chatMessage.EditedAt,
IsDeleted: chatMessage.IsDeleted,
DeletedAt: chatMessage.DeletedAt,
CreatedAt: chatMessage.CreatedAt,
UpdatedAt: chatMessage.UpdatedAt,
}
if chatMessage.Sender != nil {
result.Sender = &response.UserBasicInfo{
ID: chatMessage.Sender.ID,
Username: chatMessage.Sender.Username,
Fullname: chatMessage.Sender.Fullname,
Email: chatMessage.Sender.Email,
}
}
return result
}
// Chat Participant Mapper
func ChatParticipantResponseMapper(chatParticipant *entity.ChatParticipants) *response.ChatParticipantResponse {
result := &response.ChatParticipantResponse{
ID: chatParticipant.ID,
ChatSessionID: chatParticipant.ChatSessionID,
UserID: chatParticipant.UserID,
JoinedAt: chatParticipant.JoinedAt,
LeftAt: chatParticipant.LeftAt,
IsActive: chatParticipant.IsActive,
CreatedAt: chatParticipant.CreatedAt,
UpdatedAt: chatParticipant.UpdatedAt,
}
if chatParticipant.User != nil {
result.User = &response.UserBasicInfo{
ID: chatParticipant.User.ID,
Username: chatParticipant.User.Username,
Fullname: chatParticipant.User.Fullname,
Email: chatParticipant.User.Email,
}
}
return result
}

View File

@ -0,0 +1,272 @@
package repository
import (
"narasi-ahli-be/app/database"
"narasi-ahli-be/app/database/entity"
"narasi-ahli-be/app/module/chat/request"
"narasi-ahli-be/utils/paginator"
)
type chatRepository struct {
DB *database.Database
}
type ChatRepository interface {
// Chat Session methods
GetAllChatSessions(userId uint, req request.ChatSessionQueryRequest) (chatSessions []*entity.ChatSessions, paging paginator.Pagination, err error)
FindChatSessionByID(id uint) (chatSession *entity.ChatSessions, err error)
FindChatSessionByUserAndID(userId uint, id uint) (chatSession *entity.ChatSessions, err error)
CreateChatSession(chatSession *entity.ChatSessions) (result *entity.ChatSessions, err error)
UpdateChatSession(id uint, chatSession *entity.ChatSessions) (err error)
DeleteChatSession(id uint) (err error)
// Chat Message methods
GetAllChatMessages(req request.ChatMessageQueryRequest) (chatMessages []*entity.ChatMessages, paging paginator.Pagination, err error)
FindChatMessageByID(id uint) (chatMessage *entity.ChatMessages, err error)
FindChatMessageByUserAndID(userId uint, id uint) (chatMessage *entity.ChatMessages, err error)
CreateChatMessage(chatMessage *entity.ChatMessages) (result *entity.ChatMessages, err error)
UpdateChatMessage(id uint, chatMessage *entity.ChatMessages) (err error)
DeleteChatMessage(id uint) (err error)
// Chat Participant methods
CreateChatParticipant(chatParticipant *entity.ChatParticipants) (result *entity.ChatParticipants, err error)
FindChatParticipantsBySessionID(chatSessionID uint) (participants []*entity.ChatParticipants, err error)
UpdateChatParticipant(id uint, chatParticipant *entity.ChatParticipants) (err error)
DeleteChatParticipant(id uint) (err error)
// Utility methods
FindPersonalChatSession(userId1 uint, userId2 uint) (chatSession *entity.ChatSessions, err error)
CheckUserInChatSession(userId uint, chatSessionID uint) (isParticipant bool, err error)
}
func NewChatRepository(db *database.Database) ChatRepository {
return &chatRepository{
DB: db,
}
}
// Chat Session Repository Methods
func (_i *chatRepository) GetAllChatSessions(userId uint, req request.ChatSessionQueryRequest) (chatSessions []*entity.ChatSessions, paging paginator.Pagination, err error) {
// Get chat sessions where user is a participant
query := _i.DB.DB.Model(&entity.ChatSessions{}).
Joins("INNER JOIN chat_participants cp ON chat_sessions.id = cp.chat_session_id").
Where("cp.user_id = ? AND cp.is_active = true", userId)
// Apply filters
if req.Type != nil {
query = query.Where("chat_sessions.type = ?", *req.Type)
}
// Include relationships
query = query.Preload("Creator").
Preload("Participants", "is_active = true").
Preload("Participants.User").
Preload("Messages", "is_deleted = false").
Preload("Messages.Sender")
// Order by updated_at desc (most recent first)
query = query.Order("chat_sessions.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(&chatSessions).Error
paging = *req.Pagination
return
}
func (_i *chatRepository) FindChatSessionByID(id uint) (chatSession *entity.ChatSessions, err error) {
err = _i.DB.DB.Preload("Creator").
Preload("Participants", "is_active = true").
Preload("Participants.User").
Preload("Messages", "is_deleted = false").
Preload("Messages.Sender").
First(&chatSession, id).Error
return
}
func (_i *chatRepository) FindChatSessionByUserAndID(userId uint, id uint) (chatSession *entity.ChatSessions, err error) {
// Check if user is participant in this chat session
var isParticipant bool
err = _i.DB.DB.Table("chat_participants").
Select("COUNT(*) > 0").
Where("chat_session_id = ? AND user_id = ? AND is_active = true", id, userId).
Scan(&isParticipant).Error
if err != nil {
return nil, err
}
if !isParticipant {
return nil, nil
}
err = _i.DB.DB.Preload("Creator").
Preload("Participants", "is_active = true").
Preload("Participants.User").
Preload("Messages", "is_deleted = false").
Preload("Messages.Sender").
First(&chatSession, id).Error
return
}
func (_i *chatRepository) CreateChatSession(chatSession *entity.ChatSessions) (result *entity.ChatSessions, err error) {
err = _i.DB.DB.Create(chatSession).Error
if err != nil {
return nil, err
}
// Reload with relationships
err = _i.DB.DB.Preload("Creator").
Preload("Participants", "is_active = true").
Preload("Participants.User").
First(&result, chatSession.ID).Error
return
}
func (_i *chatRepository) UpdateChatSession(id uint, chatSession *entity.ChatSessions) (err error) {
err = _i.DB.DB.Model(&entity.ChatSessions{}).Where("id = ?", id).Updates(chatSession).Error
return
}
func (_i *chatRepository) DeleteChatSession(id uint) (err error) {
err = _i.DB.DB.Delete(&entity.ChatSessions{}, id).Error
return
}
// Chat Message Repository Methods
func (_i *chatRepository) GetAllChatMessages(req request.ChatMessageQueryRequest) (chatMessages []*entity.ChatMessages, paging paginator.Pagination, err error) {
// Check if user is participant in this chat session
var isParticipant bool
err = _i.DB.DB.Table("chat_participants").
Select("COUNT(*) > 0").
Where("chat_session_id = ? AND user_id = ? AND is_active = true", req.ChatSessionID, req.UserID).
Scan(&isParticipant).Error
if err != nil {
return nil, paginator.Pagination{}, err
}
if !isParticipant {
return nil, paginator.Pagination{}, nil
}
query := _i.DB.DB.Model(&entity.ChatMessages{}).Where("chat_session_id = ? AND is_deleted = false", req.ChatSessionID)
// Include relationships
query = query.Preload("Sender")
// Order by created_at desc (most recent first)
query = query.Order("created_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(&chatMessages).Error
paging = *req.Pagination
return
}
func (_i *chatRepository) FindChatMessageByID(id uint) (chatMessage *entity.ChatMessages, err error) {
err = _i.DB.DB.Preload("Sender").First(&chatMessage, id).Error
return
}
func (_i *chatRepository) FindChatMessageByUserAndID(userId uint, id uint) (chatMessage *entity.ChatMessages, err error) {
// Check if user is participant in the chat session of this message
var isParticipant bool
err = _i.DB.DB.Table("chat_messages cm").
Joins("INNER JOIN chat_participants cp ON cm.chat_session_id = cp.chat_session_id").
Select("COUNT(*) > 0").
Where("cm.id = ? AND cp.user_id = ? AND cp.is_active = true", id, userId).
Scan(&isParticipant).Error
if err != nil {
return nil, err
}
if !isParticipant {
return nil, nil
}
err = _i.DB.DB.Preload("Sender").First(&chatMessage, id).Error
return
}
func (_i *chatRepository) CreateChatMessage(chatMessage *entity.ChatMessages) (result *entity.ChatMessages, err error) {
err = _i.DB.DB.Create(chatMessage).Error
if err != nil {
return nil, err
}
// Reload with relationships
err = _i.DB.DB.Preload("Sender").First(&result, chatMessage.ID).Error
return
}
func (_i *chatRepository) UpdateChatMessage(id uint, chatMessage *entity.ChatMessages) (err error) {
err = _i.DB.DB.Model(&entity.ChatMessages{}).Where("id = ?", id).Updates(chatMessage).Error
return
}
func (_i *chatRepository) DeleteChatMessage(id uint) (err error) {
err = _i.DB.DB.Delete(&entity.ChatMessages{}, id).Error
return
}
// Chat Participant Repository Methods
func (_i *chatRepository) CreateChatParticipant(chatParticipant *entity.ChatParticipants) (result *entity.ChatParticipants, err error) {
err = _i.DB.DB.Create(chatParticipant).Error
if err != nil {
return nil, err
}
// Reload with relationships
err = _i.DB.DB.Preload("User").First(&result, chatParticipant.ID).Error
return
}
func (_i *chatRepository) FindChatParticipantsBySessionID(chatSessionID uint) (participants []*entity.ChatParticipants, err error) {
err = _i.DB.DB.Model(&entity.ChatParticipants{}).Where("chat_session_id = ? AND is_active = true", chatSessionID).
Preload("User").Find(&participants).Error
return
}
func (_i *chatRepository) UpdateChatParticipant(id uint, chatParticipant *entity.ChatParticipants) (err error) {
err = _i.DB.DB.Model(&entity.ChatParticipants{}).Where("id = ?", id).Updates(chatParticipant).Error
return
}
func (_i *chatRepository) DeleteChatParticipant(id uint) (err error) {
err = _i.DB.DB.Delete(&entity.ChatParticipants{}, id).Error
return
}
// Utility Methods
func (_i *chatRepository) FindPersonalChatSession(userId1 uint, userId2 uint) (chatSession *entity.ChatSessions, err error) {
err = _i.DB.DB.Model(&entity.ChatSessions{}).
Joins("INNER JOIN chat_participants cp1 ON chat_sessions.id = cp1.chat_session_id").
Joins("INNER JOIN chat_participants cp2 ON chat_sessions.id = cp2.chat_session_id").
Where("chat_sessions.type = 'personal' AND cp1.user_id = ? AND cp2.user_id = ? AND cp1.is_active = true AND cp2.is_active = true", userId1, userId2).
Preload("Creator").
Preload("Participants", "is_active = true").
Preload("Participants.User").
First(&chatSession).Error
return
}
func (_i *chatRepository) CheckUserInChatSession(userId uint, chatSessionID uint) (isParticipant bool, err error) {
err = _i.DB.DB.Table("chat_participants").
Select("COUNT(*) > 0").
Where("chat_session_id = ? AND user_id = ? AND is_active = true", chatSessionID, userId).
Scan(&isParticipant).Error
return
}

View File

@ -0,0 +1,122 @@
package request
import (
"narasi-ahli-be/app/database/entity"
"narasi-ahli-be/utils/paginator"
"strconv"
"time"
)
// Chat Session Requests
type ChatSessionQueryRequest struct {
Type *string `json:"type"` // 'personal' or 'group'
Pagination *paginator.Pagination `json:"pagination"`
}
type ChatSessionCreateRequest struct {
Name *string `json:"name" validate:"omitempty,min=2,max=255"` // null for personal chat
Type string `json:"type" validate:"required,oneof=personal group"`
UserIDs []uint `json:"userIds" validate:"required,min=1"` // participants (excluding creator)
}
func (req ChatSessionCreateRequest) ToEntity(createdBy uint) *entity.ChatSessions {
return &entity.ChatSessions{
Name: req.Name,
Type: req.Type,
CreatedBy: createdBy,
}
}
type ChatSessionUpdateRequest struct {
Name *string `json:"name" validate:"omitempty,min=2,max=255"`
}
func (req ChatSessionUpdateRequest) ToEntity() *entity.ChatSessions {
return &entity.ChatSessions{
Name: req.Name,
}
}
type ChatSessionQueryRequestContext struct {
Type string `json:"type"`
}
func (req ChatSessionQueryRequestContext) ToParamRequest() ChatSessionQueryRequest {
var request ChatSessionQueryRequest
if chatType := req.Type; chatType != "" {
request.Type = &chatType
}
return request
}
// Chat Message Requests
type ChatMessageQueryRequest struct {
ChatSessionID uint `json:"chatSessionId" validate:"required"`
UserID uint `json:"userId"` // Will be set in service layer
Pagination *paginator.Pagination `json:"pagination"`
}
type ChatMessageCreateRequest struct {
ChatSessionID uint `json:"chatSessionId" validate:"required"`
Message string `json:"message" validate:"required,min=1,max=1000"`
MessageType string `json:"messageType" validate:"omitempty,oneof=text image file user assistant"`
}
func (req ChatMessageCreateRequest) ToEntity(senderID uint) *entity.ChatMessages {
messageType := req.MessageType
if messageType == "" {
messageType = "text"
}
return &entity.ChatMessages{
ChatSessionID: req.ChatSessionID,
SenderID: senderID,
Message: req.Message,
MessageType: messageType,
}
}
type ChatMessageUpdateRequest struct {
Message string `json:"message" validate:"required,min=1,max=1000"`
}
func (req ChatMessageUpdateRequest) ToEntity() *entity.ChatMessages {
return &entity.ChatMessages{
Message: req.Message,
IsEdited: true,
EditedAt: &time.Time{},
}
}
type ChatMessageQueryRequestContext struct {
ChatSessionID string `json:"chatSessionId"`
}
func (req ChatMessageQueryRequestContext) ToParamRequest() ChatMessageQueryRequest {
var request ChatMessageQueryRequest
if chatSessionId := req.ChatSessionID; chatSessionId != "" {
chatSessionIdUint, err := strconv.ParseUint(chatSessionId, 10, 0)
if err == nil {
request.ChatSessionID = uint(chatSessionIdUint)
}
}
return request
}
// Chat Participant Requests
type ChatParticipantCreateRequest struct {
ChatSessionID uint `json:"chatSessionId" validate:"required"`
UserID uint `json:"userId" validate:"required"`
}
func (req ChatParticipantCreateRequest) ToEntity() *entity.ChatParticipants {
return &entity.ChatParticipants{
ChatSessionID: req.ChatSessionID,
UserID: req.UserID,
IsActive: true,
}
}

View File

@ -0,0 +1,56 @@
package response
import (
"time"
)
// Chat Session Response
type ChatSessionResponse struct {
ID uint `json:"id"`
Name *string `json:"name"`
Type string `json:"type"`
CreatedBy uint `json:"createdBy"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Creator *UserBasicInfo `json:"creator,omitempty"`
Participants []*ChatParticipantResponse `json:"participants,omitempty"`
LastMessage *ChatMessageResponse `json:"lastMessage,omitempty"`
UnreadCount int `json:"unreadCount"`
}
// Chat Message Response
type ChatMessageResponse struct {
ID uint `json:"id"`
ChatSessionID uint `json:"chatSessionId"`
SenderID uint `json:"senderId"`
Message string `json:"message"`
MessageType string `json:"messageType"`
IsEdited bool `json:"isEdited"`
EditedAt *time.Time `json:"editedAt"`
IsDeleted bool `json:"isDeleted"`
DeletedAt *time.Time `json:"deletedAt"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
Sender *UserBasicInfo `json:"sender,omitempty"`
}
// Chat Participant Response
type ChatParticipantResponse struct {
ID uint `json:"id"`
ChatSessionID uint `json:"chatSessionId"`
UserID uint `json:"userId"`
JoinedAt time.Time `json:"joinedAt"`
LeftAt *time.Time `json:"leftAt"`
IsActive bool `json:"isActive"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
User *UserBasicInfo `json:"user,omitempty"`
}
// User Basic Info (reused from work_history)
type UserBasicInfo struct {
ID uint `json:"id"`
Username string `json:"username"`
Fullname string `json:"fullname"`
Email string `json:"email"`
}

View File

@ -0,0 +1,418 @@
package service
import (
"errors"
"narasi-ahli-be/app/database/entity"
"narasi-ahli-be/app/module/chat/mapper"
"narasi-ahli-be/app/module/chat/repository"
"narasi-ahli-be/app/module/chat/request"
"narasi-ahli-be/app/module/chat/response"
usersRepository "narasi-ahli-be/app/module/users/repository"
"narasi-ahli-be/utils/paginator"
utilSvc "narasi-ahli-be/utils/service"
"time"
"github.com/rs/zerolog"
)
type chatService struct {
Repo repository.ChatRepository
UsersRepo usersRepository.UsersRepository
Log zerolog.Logger
}
type ChatService interface {
// Chat Session methods
GetAllChatSessions(authToken string, req request.ChatSessionQueryRequest) (chatSessions []*response.ChatSessionResponse, paging paginator.Pagination, err error)
GetChatSessionByID(authToken string, id uint) (chatSession *response.ChatSessionResponse, err error)
CreateChatSession(authToken string, req request.ChatSessionCreateRequest) (chatSession *response.ChatSessionResponse, err error)
UpdateChatSession(authToken string, id uint, req request.ChatSessionUpdateRequest) (err error)
DeleteChatSession(authToken string, id uint) error
// Chat Message methods
GetAllChatMessages(authToken string, req request.ChatMessageQueryRequest) (chatMessages []*response.ChatMessageResponse, paging paginator.Pagination, err error)
GetChatMessageByID(authToken string, id uint) (chatMessage *response.ChatMessageResponse, err error)
CreateChatMessage(authToken string, req request.ChatMessageCreateRequest) (chatMessage *response.ChatMessageResponse, err error)
UpdateChatMessage(authToken string, id uint, req request.ChatMessageUpdateRequest) (err error)
DeleteChatMessage(authToken string, id uint) error
// Chat Participant methods
AddParticipantToChat(authToken string, chatSessionID uint, participantUserID uint) error
RemoveParticipantFromChat(authToken string, chatSessionID uint, participantUserID uint) error
}
func NewChatService(repo repository.ChatRepository, usersRepo usersRepository.UsersRepository, log zerolog.Logger) ChatService {
return &chatService{
Repo: repo,
UsersRepo: usersRepo,
Log: log,
}
}
// Chat Session Service Methods
func (_i *chatService) GetAllChatSessions(authToken string, req request.ChatSessionQueryRequest) (chatSessions []*response.ChatSessionResponse, paging paginator.Pagination, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, paginator.Pagination{}, errors.New("user not found")
}
results, paging, err := _i.Repo.GetAllChatSessions(userInfo.ID, req)
if err != nil {
return
}
for _, result := range results {
chatSessions = append(chatSessions, mapper.ChatSessionResponseMapper(result))
}
return
}
func (_i *chatService) GetChatSessionByID(authToken string, id uint) (chatSession *response.ChatSessionResponse, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, errors.New("user not found")
}
result, err := _i.Repo.FindChatSessionByUserAndID(userInfo.ID, id)
if err != nil {
return nil, err
}
if result == nil {
return nil, errors.New("chat session not found or access denied")
}
return mapper.ChatSessionResponseMapper(result), nil
}
func (_i *chatService) CreateChatSession(authToken string, req request.ChatSessionCreateRequest) (chatSession *response.ChatSessionResponse, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, errors.New("user not found")
}
_i.Log.Info().Interface("data", req).Msg("Creating chat session")
// Validate business rules
if req.Type == "personal" && len(req.UserIDs) != 1 {
return nil, errors.New("personal chat must have exactly one other participant")
}
if req.Type == "group" && len(req.UserIDs) < 1 {
return nil, errors.New("group chat must have at least one participant")
}
// Check if personal chat already exists
if req.Type == "personal" {
existingChat, err := _i.Repo.FindPersonalChatSession(userInfo.ID, req.UserIDs[0])
if err == nil && existingChat != nil {
return mapper.ChatSessionResponseMapper(existingChat), nil
}
}
// Validate all user IDs exist
for _, userID := range req.UserIDs {
user, err := _i.UsersRepo.FindOne(userID)
if err != nil || user == nil {
return nil, errors.New("invalid user ID: " + string(rune(userID)))
}
}
// Create chat session
entity := req.ToEntity(userInfo.ID)
result, err := _i.Repo.CreateChatSession(entity)
if err != nil {
return nil, err
}
// Add creator as participant
creatorParticipant := &request.ChatParticipantCreateRequest{
ChatSessionID: result.ID,
UserID: userInfo.ID,
}
_, err = _i.Repo.CreateChatParticipant(creatorParticipant.ToEntity())
if err != nil {
return nil, err
}
// Add other participants
for _, userID := range req.UserIDs {
participant := &request.ChatParticipantCreateRequest{
ChatSessionID: result.ID,
UserID: userID,
}
_, err = _i.Repo.CreateChatParticipant(participant.ToEntity())
if err != nil {
return nil, err
}
}
// Reload with all relationships
finalResult, err := _i.Repo.FindChatSessionByID(result.ID)
if err != nil {
return nil, err
}
return mapper.ChatSessionResponseMapper(finalResult), nil
}
func (_i *chatService) UpdateChatSession(authToken string, id uint, req request.ChatSessionUpdateRequest) (err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return errors.New("user not found")
}
_i.Log.Info().Interface("data", req).Msg("Updating chat session")
// Check if chat session exists and user has access
existing, err := _i.Repo.FindChatSessionByUserAndID(userInfo.ID, id)
if err != nil {
return err
}
if existing == nil {
return errors.New("chat session not found or access denied")
}
// Only creator can update chat session
if existing.CreatedBy != userInfo.ID {
return errors.New("only chat creator can update chat session")
}
entity := req.ToEntity()
return _i.Repo.UpdateChatSession(id, entity)
}
func (_i *chatService) DeleteChatSession(authToken string, id uint) error {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return errors.New("user not found")
}
_i.Log.Info().Uint("userId", userInfo.ID).Uint("id", id).Msg("Deleting chat session")
// Check if chat session exists and user has access
existing, err := _i.Repo.FindChatSessionByUserAndID(userInfo.ID, id)
if err != nil {
return err
}
if existing == nil {
return errors.New("chat session not found or access denied")
}
// Only creator can delete chat session
if existing.CreatedBy != userInfo.ID {
return errors.New("only chat creator can delete chat session")
}
return _i.Repo.DeleteChatSession(id)
}
// Chat Message Service Methods
func (_i *chatService) GetAllChatMessages(authToken string, req request.ChatMessageQueryRequest) (chatMessages []*response.ChatMessageResponse, paging paginator.Pagination, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, paginator.Pagination{}, errors.New("user not found")
}
// Set user ID in request for repository
req.UserID = userInfo.ID
results, paging, err := _i.Repo.GetAllChatMessages(req)
if err != nil {
return
}
for _, result := range results {
chatMessages = append(chatMessages, mapper.ChatMessageResponseMapper(result))
}
return
}
func (_i *chatService) GetChatMessageByID(authToken string, id uint) (chatMessage *response.ChatMessageResponse, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, errors.New("user not found")
}
result, err := _i.Repo.FindChatMessageByUserAndID(userInfo.ID, id)
if err != nil {
return nil, err
}
if result == nil {
return nil, errors.New("chat message not found or access denied")
}
return mapper.ChatMessageResponseMapper(result), nil
}
func (_i *chatService) CreateChatMessage(authToken string, req request.ChatMessageCreateRequest) (chatMessage *response.ChatMessageResponse, err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return nil, errors.New("user not found")
}
_i.Log.Info().Interface("data", req).Msg("Creating chat message")
// Check if user is participant in the chat session
isParticipant, err := _i.Repo.CheckUserInChatSession(userInfo.ID, req.ChatSessionID)
if err != nil {
return nil, err
}
if !isParticipant {
return nil, errors.New("user is not a participant in this chat session")
}
entity := req.ToEntity(userInfo.ID)
result, err := _i.Repo.CreateChatMessage(entity)
if err != nil {
return nil, err
}
return mapper.ChatMessageResponseMapper(result), nil
}
func (_i *chatService) UpdateChatMessage(authToken string, id uint, req request.ChatMessageUpdateRequest) (err error) {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return errors.New("user not found")
}
_i.Log.Info().Interface("data", req).Msg("Updating chat message")
// Check if message exists and user has access
existing, err := _i.Repo.FindChatMessageByUserAndID(userInfo.ID, id)
if err != nil {
return err
}
if existing == nil {
return errors.New("chat message not found or access denied")
}
// Only sender can update message
if existing.SenderID != userInfo.ID {
return errors.New("only message sender can update message")
}
now := time.Now()
entity := req.ToEntity()
entity.EditedAt = &now
return _i.Repo.UpdateChatMessage(id, entity)
}
func (_i *chatService) DeleteChatMessage(authToken string, id uint) error {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return errors.New("user not found")
}
_i.Log.Info().Uint("userId", userInfo.ID).Uint("id", id).Msg("Deleting chat message")
// Check if message exists and user has access
existing, err := _i.Repo.FindChatMessageByUserAndID(userInfo.ID, id)
if err != nil {
return err
}
if existing == nil {
return errors.New("chat message not found or access denied")
}
// Only sender can delete message
if existing.SenderID != userInfo.ID {
return errors.New("only message sender can delete message")
}
return _i.Repo.DeleteChatMessage(id)
}
// Chat Participant Service Methods
func (_i *chatService) AddParticipantToChat(authToken string, chatSessionID uint, participantUserID uint) error {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return errors.New("user not found")
}
_i.Log.Info().Uint("userId", userInfo.ID).Uint("chatSessionID", chatSessionID).Uint("participantUserID", participantUserID).Msg("Adding participant to chat")
// Check if user has access to chat session
existing, err := _i.Repo.FindChatSessionByUserAndID(userInfo.ID, chatSessionID)
if err != nil {
return err
}
if existing == nil {
return errors.New("chat session not found or access denied")
}
// Only creator can add participants
if existing.CreatedBy != userInfo.ID {
return errors.New("only chat creator can add participants")
}
// Validate participant user exists
user, err := _i.UsersRepo.FindOne(participantUserID)
if err != nil || user == nil {
return errors.New("invalid user ID")
}
// Check if user is already a participant
isParticipant, err := _i.Repo.CheckUserInChatSession(participantUserID, chatSessionID)
if err != nil {
return err
}
if isParticipant {
return errors.New("user is already a participant in this chat")
}
participant := &request.ChatParticipantCreateRequest{
ChatSessionID: chatSessionID,
UserID: participantUserID,
}
_, err = _i.Repo.CreateChatParticipant(participant.ToEntity())
return err
}
func (_i *chatService) RemoveParticipantFromChat(authToken string, chatSessionID uint, participantUserID uint) error {
userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
if userInfo == nil {
return errors.New("user not found")
}
_i.Log.Info().Uint("userId", userInfo.ID).Uint("chatSessionID", chatSessionID).Uint("participantUserID", participantUserID).Msg("Removing participant from chat")
// Check if user has access to chat session
existing, err := _i.Repo.FindChatSessionByUserAndID(userInfo.ID, chatSessionID)
if err != nil {
return err
}
if existing == nil {
return errors.New("chat session not found or access denied")
}
// Only creator can remove participants (or user can remove themselves)
if existing.CreatedBy != userInfo.ID && userInfo.ID != participantUserID {
return errors.New("only chat creator can remove participants or user can remove themselves")
}
// Find participant
participants, err := _i.Repo.FindChatParticipantsBySessionID(chatSessionID)
if err != nil {
return err
}
var participantToRemove *entity.ChatParticipants
for _, participant := range participants {
if participant.UserID == participantUserID {
participantToRemove = participant
break
}
}
if participantToRemove == nil {
return errors.New("participant not found")
}
// Soft delete by setting is_active to false
now := time.Now()
participantToRemove.IsActive = false
participantToRemove.LeftAt = &now
return _i.Repo.UpdateChatParticipant(participantToRemove.ID, participantToRemove)
}

View File

@ -39,14 +39,14 @@ func ConversationsResponseMapper(conversation *entity.Conversations) *response.C
func ChatMessagesResponseMapper(chatMessage *entity.ChatMessages) *response.ChatMessagesResponse {
result := &response.ChatMessagesResponse{
ID: chatMessage.ID,
ConversationID: chatMessage.ConversationID,
ConversationID: chatMessage.ChatSessionID,
SenderID: chatMessage.SenderID,
MessageText: chatMessage.MessageText,
MessageText: &chatMessage.Message,
MessageType: chatMessage.MessageType,
FileURL: chatMessage.FileURL,
FileName: chatMessage.FileName,
FileSize: chatMessage.FileSize,
IsRead: chatMessage.IsRead,
FileURL: nil, // Not available in entity
FileName: nil, // Not available in entity
FileSize: nil, // Not available in entity
IsRead: false, // Not available in entity, default to false
CreatedAt: chatMessage.CreatedAt,
}

View File

@ -6,7 +6,7 @@ import (
"narasi-ahli-be/utils/paginator"
"time"
"gorm.io/gorm"
"gorm.io/gorm"
)
type communicationsRepository struct {

View File

@ -23,42 +23,40 @@ func (req ConversationsCreateRequest) ToEntity() *entity.Conversations {
// Chat Messages Request DTOs
type ChatMessagesQueryRequest struct {
ConversationID uint `json:"conversationId" validate:"required"`
Pagination *paginator.Pagination `json:"pagination"`
ChatSessionID uint `json:"chatSessionId" validate:"required"`
Pagination *paginator.Pagination `json:"pagination"`
}
type ChatMessagesCreateRequest struct {
ConversationID uint `json:"conversationId" validate:"required"`
MessageText string `json:"messageText"`
MessageType string `json:"messageType" validate:"required,oneof=text image file audio"`
ChatSessionID uint `json:"chatSessionId" validate:"required"`
Message string `json:"message"`
MessageType string `json:"messageType" validate:"required,oneof=text image file audio"`
}
func (req ChatMessagesCreateRequest) ToEntity() *entity.ChatMessages {
return &entity.ChatMessages{
ConversationID: req.ConversationID,
SenderID: 0, // Will be set in service layer
MessageText: &req.MessageText,
MessageType: req.MessageType,
IsRead: false,
ChatSessionID: req.ChatSessionID,
SenderID: 0, // Will be set in service layer
Message: req.Message,
MessageType: req.MessageType,
}
}
type ChatMessagesFileUploadRequest struct {
ConversationID uint `json:"conversationId" validate:"required"`
MessageType string `json:"messageType" validate:"required,oneof=image file audio"`
FileName string `json:"fileName"`
FileSize int64 `json:"fileSize"`
ChatSessionID uint `json:"chatSessionId" validate:"required"`
MessageType string `json:"messageType" validate:"required,oneof=image file audio"`
FileName string `json:"fileName"`
FileSize int64 `json:"fileSize"`
}
func (req ChatMessagesFileUploadRequest) ToEntity(fileURL string) *entity.ChatMessages {
return &entity.ChatMessages{
ConversationID: req.ConversationID,
SenderID: 0, // Will be set in service layer
MessageType: req.MessageType,
FileURL: &fileURL,
FileName: &req.FileName,
FileSize: &req.FileSize,
IsRead: false,
ChatSessionID: req.ChatSessionID,
SenderID: 0, // Will be set in service layer
Message: "", // File messages might not have text content
MessageType: req.MessageType,
// Note: FileURL, FileName, FileSize, IsRead fields are not available in the entity
// These would need to be stored separately or the entity needs to be updated
}
}
@ -75,14 +73,14 @@ func (req ConversationsQueryRequestContext) ToParamRequest() ConversationsQueryR
}
type ChatMessagesQueryRequestContext struct {
ConversationID string `json:"conversationId"`
ChatSessionID string `json:"chatSessionId"`
}
func (req ChatMessagesQueryRequestContext) ToParamRequest() ChatMessagesQueryRequest {
var request ChatMessagesQueryRequest
if conversationIDStr := req.ConversationID; conversationIDStr != "" {
// Parse conversation ID from string to uint
if chatSessionIDStr := req.ChatSessionID; chatSessionIDStr != "" {
// Parse chat session ID from string to uint
// This will be handled in the controller
}

View File

@ -10,6 +10,7 @@ import (
"narasi-ahli-be/app/module/article_comments"
"narasi-ahli-be/app/module/article_files"
"narasi-ahli-be/app/module/articles"
"narasi-ahli-be/app/module/chat"
"narasi-ahli-be/app/module/cities"
"narasi-ahli-be/app/module/custom_static_pages"
"narasi-ahli-be/app/module/districts"
@ -48,6 +49,7 @@ type Router struct {
ArticleCommentsRouter *article_comments.ArticleCommentsRouter
ArticleApprovalsRouter *article_approvals.ArticleApprovalsRouter
ArticlesRouter *articles.ArticlesRouter
ChatRouter *chat.ChatRouter
CitiesRouter *cities.CitiesRouter
CustomStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter
DistrictsRouter *districts.DistrictsRouter
@ -81,6 +83,7 @@ func NewRouter(
articleCommentsRouter *article_comments.ArticleCommentsRouter,
articleApprovalsRouter *article_approvals.ArticleApprovalsRouter,
articlesRouter *articles.ArticlesRouter,
chatRouter *chat.ChatRouter,
citiesRouter *cities.CitiesRouter,
customStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter,
districtsRouter *districts.DistrictsRouter,
@ -112,6 +115,7 @@ func NewRouter(
ArticleCommentsRouter: articleCommentsRouter,
ArticleApprovalsRouter: articleApprovalsRouter,
ArticlesRouter: articlesRouter,
ChatRouter: chatRouter,
CitiesRouter: citiesRouter,
CustomStaticPagesRouter: customStaticPagesRouter,
DistrictsRouter: districtsRouter,
@ -153,6 +157,7 @@ func (r *Router) Register() {
r.ArticleApprovalsRouter.RegisterArticleApprovalsRoutes()
r.ArticlesRouter.RegisterArticlesRoutes()
r.ArticleCommentsRouter.RegisterArticleCommentsRoutes()
r.ChatRouter.RegisterChatRoutes()
r.CitiesRouter.RegisterCitiesRoutes()
r.CustomStaticPagesRouter.RegisterCustomStaticPagesRoutes()
r.DistrictsRouter.RegisterDistrictsRoutes()

View File

@ -4536,6 +4536,835 @@ const docTemplate = `{
}
}
},
"/chat/messages": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for getting all messages in a specific chat session",
"tags": [
"Chat"
],
"summary": "Get all chat messages",
"parameters": [
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Session ID",
"name": "chatSessionId",
"in": "query",
"required": true
},
{
"type": "integer",
"name": "count",
"in": "query"
},
{
"type": "integer",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"name": "nextPage",
"in": "query"
},
{
"type": "integer",
"name": "page",
"in": "query"
},
{
"type": "integer",
"name": "previousPage",
"in": "query"
},
{
"type": "string",
"name": "sort",
"in": "query"
},
{
"type": "string",
"name": "sortBy",
"in": "query"
},
{
"type": "integer",
"name": "totalPage",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
},
"post": {
"security": [
{
"Bearer": []
}
],
"description": "API for creating a new chat message",
"tags": [
"Chat"
],
"summary": "Create chat message",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"description": "Required payload",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.ChatMessageCreateRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/chat/messages/{id}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for getting one chat message",
"tags": [
"Chat"
],
"summary": "Get one chat message",
"parameters": [
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Message ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
},
"put": {
"security": [
{
"Bearer": []
}
],
"description": "API for updating chat message (only sender can update)",
"tags": [
"Chat"
],
"summary": "Update chat message",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Message ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Required payload",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.ChatMessageUpdateRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"description": "API for deleting chat message (only sender can delete)",
"tags": [
"Chat"
],
"summary": "Delete chat message",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Message ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/chat/sessions": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for getting all chat sessions for authenticated user",
"tags": [
"Chat"
],
"summary": "Get all chat sessions",
"parameters": [
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "string",
"description": "Chat type (personal or group)",
"name": "type",
"in": "query"
},
{
"type": "integer",
"name": "count",
"in": "query"
},
{
"type": "integer",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"name": "nextPage",
"in": "query"
},
{
"type": "integer",
"name": "page",
"in": "query"
},
{
"type": "integer",
"name": "previousPage",
"in": "query"
},
{
"type": "string",
"name": "sort",
"in": "query"
},
{
"type": "string",
"name": "sortBy",
"in": "query"
},
{
"type": "integer",
"name": "totalPage",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
},
"post": {
"security": [
{
"Bearer": []
}
],
"description": "API for creating a new chat session (personal or group)",
"tags": [
"Chat"
],
"summary": "Create chat session",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"description": "Required payload",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.ChatSessionCreateRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/chat/sessions/{chatSessionId}/participants": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "API for adding a participant to a chat session (only creator can add)",
"tags": [
"Chat"
],
"summary": "Add participant to chat session",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Session ID",
"name": "chatSessionId",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "Participant User ID",
"name": "participantUserId",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"description": "API for removing a participant from a chat session (creator can remove anyone, user can remove themselves)",
"tags": [
"Chat"
],
"summary": "Remove participant from chat session",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Session ID",
"name": "chatSessionId",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "Participant User ID",
"name": "participantUserId",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/chat/sessions/{id}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for getting one chat session",
"tags": [
"Chat"
],
"summary": "Get one chat session",
"parameters": [
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Session ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
},
"put": {
"security": [
{
"Bearer": []
}
],
"description": "API for updating chat session (only creator can update)",
"tags": [
"Chat"
],
"summary": "Update chat session",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Session ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Required payload",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.ChatSessionUpdateRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"description": "API for deleting chat session (only creator can delete)",
"tags": [
"Chat"
],
"summary": "Delete chat session",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Session ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/cities": {
"get": {
"security": [
@ -13763,6 +14592,86 @@ const docTemplate = `{
}
}
},
"request.ChatMessageCreateRequest": {
"type": "object",
"required": [
"chatSessionId",
"message"
],
"properties": {
"chatSessionId": {
"type": "integer"
},
"message": {
"type": "string",
"maxLength": 1000,
"minLength": 1
},
"messageType": {
"type": "string",
"enum": [
"text",
"image",
"file",
"user",
"assistant"
]
}
}
},
"request.ChatMessageUpdateRequest": {
"type": "object",
"required": [
"message"
],
"properties": {
"message": {
"type": "string",
"maxLength": 1000,
"minLength": 1
}
}
},
"request.ChatSessionCreateRequest": {
"type": "object",
"required": [
"type",
"userIds"
],
"properties": {
"name": {
"description": "null for personal chat",
"type": "string",
"maxLength": 255,
"minLength": 2
},
"type": {
"type": "string",
"enum": [
"personal",
"group"
]
},
"userIds": {
"description": "participants (excluding creator)",
"type": "array",
"minItems": 1,
"items": {
"type": "integer"
}
}
}
},
"request.ChatSessionUpdateRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 255,
"minLength": 2
}
}
},
"request.CitiesCreateRequest": {
"type": "object",
"required": [

View File

@ -4525,6 +4525,835 @@
}
}
},
"/chat/messages": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for getting all messages in a specific chat session",
"tags": [
"Chat"
],
"summary": "Get all chat messages",
"parameters": [
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Session ID",
"name": "chatSessionId",
"in": "query",
"required": true
},
{
"type": "integer",
"name": "count",
"in": "query"
},
{
"type": "integer",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"name": "nextPage",
"in": "query"
},
{
"type": "integer",
"name": "page",
"in": "query"
},
{
"type": "integer",
"name": "previousPage",
"in": "query"
},
{
"type": "string",
"name": "sort",
"in": "query"
},
{
"type": "string",
"name": "sortBy",
"in": "query"
},
{
"type": "integer",
"name": "totalPage",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
},
"post": {
"security": [
{
"Bearer": []
}
],
"description": "API for creating a new chat message",
"tags": [
"Chat"
],
"summary": "Create chat message",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"description": "Required payload",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.ChatMessageCreateRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/chat/messages/{id}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for getting one chat message",
"tags": [
"Chat"
],
"summary": "Get one chat message",
"parameters": [
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Message ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
},
"put": {
"security": [
{
"Bearer": []
}
],
"description": "API for updating chat message (only sender can update)",
"tags": [
"Chat"
],
"summary": "Update chat message",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Message ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Required payload",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.ChatMessageUpdateRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"description": "API for deleting chat message (only sender can delete)",
"tags": [
"Chat"
],
"summary": "Delete chat message",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Message ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/chat/sessions": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for getting all chat sessions for authenticated user",
"tags": [
"Chat"
],
"summary": "Get all chat sessions",
"parameters": [
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "string",
"description": "Chat type (personal or group)",
"name": "type",
"in": "query"
},
{
"type": "integer",
"name": "count",
"in": "query"
},
{
"type": "integer",
"name": "limit",
"in": "query"
},
{
"type": "integer",
"name": "nextPage",
"in": "query"
},
{
"type": "integer",
"name": "page",
"in": "query"
},
{
"type": "integer",
"name": "previousPage",
"in": "query"
},
{
"type": "string",
"name": "sort",
"in": "query"
},
{
"type": "string",
"name": "sortBy",
"in": "query"
},
{
"type": "integer",
"name": "totalPage",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
},
"post": {
"security": [
{
"Bearer": []
}
],
"description": "API for creating a new chat session (personal or group)",
"tags": [
"Chat"
],
"summary": "Create chat session",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"description": "Required payload",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.ChatSessionCreateRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/chat/sessions/{chatSessionId}/participants": {
"post": {
"security": [
{
"Bearer": []
}
],
"description": "API for adding a participant to a chat session (only creator can add)",
"tags": [
"Chat"
],
"summary": "Add participant to chat session",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Session ID",
"name": "chatSessionId",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "Participant User ID",
"name": "participantUserId",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"description": "API for removing a participant from a chat session (creator can remove anyone, user can remove themselves)",
"tags": [
"Chat"
],
"summary": "Remove participant from chat session",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Session ID",
"name": "chatSessionId",
"in": "path",
"required": true
},
{
"type": "integer",
"description": "Participant User ID",
"name": "participantUserId",
"in": "query",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/chat/sessions/{id}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for getting one chat session",
"tags": [
"Chat"
],
"summary": "Get one chat session",
"parameters": [
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Session ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
},
"put": {
"security": [
{
"Bearer": []
}
],
"description": "API for updating chat session (only creator can update)",
"tags": [
"Chat"
],
"summary": "Update chat session",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Session ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "Required payload",
"name": "payload",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/request.ChatSessionUpdateRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
},
"delete": {
"security": [
{
"Bearer": []
}
],
"description": "API for deleting chat session (only creator can delete)",
"tags": [
"Chat"
],
"summary": "Delete chat session",
"parameters": [
{
"type": "string",
"description": "Insert the X-Csrf-Token",
"name": "X-Csrf-Token",
"in": "header",
"required": true
},
{
"type": "string",
"default": "Bearer \u003cAdd access token here\u003e",
"description": "Insert your access token",
"name": "Authorization",
"in": "header"
},
{
"type": "integer",
"description": "Chat Session ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/response.Response"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/cities": {
"get": {
"security": [
@ -13752,6 +14581,86 @@
}
}
},
"request.ChatMessageCreateRequest": {
"type": "object",
"required": [
"chatSessionId",
"message"
],
"properties": {
"chatSessionId": {
"type": "integer"
},
"message": {
"type": "string",
"maxLength": 1000,
"minLength": 1
},
"messageType": {
"type": "string",
"enum": [
"text",
"image",
"file",
"user",
"assistant"
]
}
}
},
"request.ChatMessageUpdateRequest": {
"type": "object",
"required": [
"message"
],
"properties": {
"message": {
"type": "string",
"maxLength": 1000,
"minLength": 1
}
}
},
"request.ChatSessionCreateRequest": {
"type": "object",
"required": [
"type",
"userIds"
],
"properties": {
"name": {
"description": "null for personal chat",
"type": "string",
"maxLength": 255,
"minLength": 2
},
"type": {
"type": "string",
"enum": [
"personal",
"group"
]
},
"userIds": {
"description": "participants (excluding creator)",
"type": "array",
"minItems": 1,
"items": {
"type": "integer"
}
}
}
},
"request.ChatSessionUpdateRequest": {
"type": "object",
"properties": {
"name": {
"type": "string",
"maxLength": 255,
"minLength": 2
}
}
},
"request.CitiesCreateRequest": {
"type": "object",
"required": [

View File

@ -378,6 +378,64 @@ definitions:
- title
- typeId
type: object
request.ChatMessageCreateRequest:
properties:
chatSessionId:
type: integer
message:
maxLength: 1000
minLength: 1
type: string
messageType:
enum:
- text
- image
- file
- user
- assistant
type: string
required:
- chatSessionId
- message
type: object
request.ChatMessageUpdateRequest:
properties:
message:
maxLength: 1000
minLength: 1
type: string
required:
- message
type: object
request.ChatSessionCreateRequest:
properties:
name:
description: null for personal chat
maxLength: 255
minLength: 2
type: string
type:
enum:
- personal
- group
type: string
userIds:
description: participants (excluding creator)
items:
type: integer
minItems: 1
type: array
required:
- type
- userIds
type: object
request.ChatSessionUpdateRequest:
properties:
name:
maxLength: 255
minLength: 2
type: string
type: object
request.CitiesCreateRequest:
properties:
cityName:
@ -4077,6 +4135,544 @@ paths:
summary: Viewer Articles Thumbnail
tags:
- Articles
/chat/messages:
get:
description: API for getting all messages in a specific chat session
parameters:
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Session ID
in: query
name: chatSessionId
required: true
type: integer
- in: query
name: count
type: integer
- in: query
name: limit
type: integer
- in: query
name: nextPage
type: integer
- in: query
name: page
type: integer
- in: query
name: previousPage
type: integer
- in: query
name: sort
type: string
- in: query
name: sortBy
type: string
- in: query
name: totalPage
type: integer
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Get all chat messages
tags:
- Chat
post:
description: API for creating a new chat message
parameters:
- description: Insert the X-Csrf-Token
in: header
name: X-Csrf-Token
required: true
type: string
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Required payload
in: body
name: payload
required: true
schema:
$ref: '#/definitions/request.ChatMessageCreateRequest'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Create chat message
tags:
- Chat
/chat/messages/{id}:
delete:
description: API for deleting chat message (only sender can delete)
parameters:
- description: Insert the X-Csrf-Token
in: header
name: X-Csrf-Token
required: true
type: string
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Message ID
in: path
name: id
required: true
type: integer
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Delete chat message
tags:
- Chat
get:
description: API for getting one chat message
parameters:
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Message ID
in: path
name: id
required: true
type: integer
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Get one chat message
tags:
- Chat
put:
description: API for updating chat message (only sender can update)
parameters:
- description: Insert the X-Csrf-Token
in: header
name: X-Csrf-Token
required: true
type: string
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Message ID
in: path
name: id
required: true
type: integer
- description: Required payload
in: body
name: payload
required: true
schema:
$ref: '#/definitions/request.ChatMessageUpdateRequest'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Update chat message
tags:
- Chat
/chat/sessions:
get:
description: API for getting all chat sessions for authenticated user
parameters:
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat type (personal or group)
in: query
name: type
type: string
- in: query
name: count
type: integer
- in: query
name: limit
type: integer
- in: query
name: nextPage
type: integer
- in: query
name: page
type: integer
- in: query
name: previousPage
type: integer
- in: query
name: sort
type: string
- in: query
name: sortBy
type: string
- in: query
name: totalPage
type: integer
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Get all chat sessions
tags:
- Chat
post:
description: API for creating a new chat session (personal or group)
parameters:
- description: Insert the X-Csrf-Token
in: header
name: X-Csrf-Token
required: true
type: string
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Required payload
in: body
name: payload
required: true
schema:
$ref: '#/definitions/request.ChatSessionCreateRequest'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Create chat session
tags:
- Chat
/chat/sessions/{chatSessionId}/participants:
delete:
description: API for removing a participant from a chat session (creator can
remove anyone, user can remove themselves)
parameters:
- description: Insert the X-Csrf-Token
in: header
name: X-Csrf-Token
required: true
type: string
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Session ID
in: path
name: chatSessionId
required: true
type: integer
- description: Participant User ID
in: query
name: participantUserId
required: true
type: integer
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Remove participant from chat session
tags:
- Chat
post:
description: API for adding a participant to a chat session (only creator can
add)
parameters:
- description: Insert the X-Csrf-Token
in: header
name: X-Csrf-Token
required: true
type: string
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Session ID
in: path
name: chatSessionId
required: true
type: integer
- description: Participant User ID
in: query
name: participantUserId
required: true
type: integer
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Add participant to chat session
tags:
- Chat
/chat/sessions/{id}:
delete:
description: API for deleting chat session (only creator can delete)
parameters:
- description: Insert the X-Csrf-Token
in: header
name: X-Csrf-Token
required: true
type: string
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Session ID
in: path
name: id
required: true
type: integer
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Delete chat session
tags:
- Chat
get:
description: API for getting one chat session
parameters:
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Session ID
in: path
name: id
required: true
type: integer
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Get one chat session
tags:
- Chat
put:
description: API for updating chat session (only creator can update)
parameters:
- description: Insert the X-Csrf-Token
in: header
name: X-Csrf-Token
required: true
type: string
- default: Bearer <Add access token here>
description: Insert your access token
in: header
name: Authorization
type: string
- description: Chat Session ID
in: path
name: id
required: true
type: integer
- description: Required payload
in: body
name: payload
required: true
schema:
$ref: '#/definitions/request.ChatSessionUpdateRequest'
responses:
"200":
description: OK
schema:
$ref: '#/definitions/response.Response'
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Update chat session
tags:
- Chat
/cities:
get:
description: API for getting all Cities

View File

@ -12,6 +12,7 @@ import (
"narasi-ahli-be/app/module/article_comments"
"narasi-ahli-be/app/module/article_files"
"narasi-ahli-be/app/module/articles"
"narasi-ahli-be/app/module/chat"
"narasi-ahli-be/app/module/cities"
"narasi-ahli-be/app/module/custom_static_pages"
"narasi-ahli-be/app/module/districts"
@ -74,6 +75,7 @@ func main() {
article_approvals.NewArticleApprovalsModule,
articles.NewArticlesModule,
article_comments.NewArticleCommentsModule,
chat.NewChatModule,
cities.NewCitiesModule,
custom_static_pages.NewCustomStaticPagesModule,
districts.NewDistrictsModule,

View File

@ -1,28 +0,0 @@
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
}

View File

@ -1,53 +0,0 @@
-- 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;