Add document saved

This commit is contained in:
Aditya Siregar
2025-08-29 16:10:05 +07:00
parent 592fa97be7
commit 2bdce63852
33 changed files with 2862 additions and 132 deletions
+5 -1
View File
@@ -120,6 +120,9 @@ func (h *LetterHandler) ListIncomingLetters(c *gin.Context) {
}
func (h *LetterHandler) parseListRequest(c *gin.Context) *contract.ListIncomingLettersRequest {
//appCtx := appcontext.FromGinContext(c)
//departmentID := appCtx.DepartmentID
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10"))
@@ -139,7 +142,6 @@ func (h *LetterHandler) parseListRequest(c *gin.Context) *contract.ListIncomingL
Limit: limit,
}
// Handle optional query parameters
if status := c.Query("status"); status != "" {
req.Status = &status
}
@@ -147,6 +149,8 @@ func (h *LetterHandler) parseListRequest(c *gin.Context) *contract.ListIncomingL
req.Query = &query
}
//req.DepartmentID = &departmentID
return req
}
@@ -10,6 +10,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"gorm.io/gorm"
)
type LetterOutgoingService interface {
@@ -35,6 +36,9 @@ type LetterOutgoingService interface {
CreateDiscussion(ctx context.Context, letterID uuid.UUID, req *contract.CreateDiscussionRequest) (*contract.DiscussionResponse, error)
UpdateDiscussion(ctx context.Context, discussionID uuid.UUID, req *contract.UpdateDiscussionRequest) error
DeleteDiscussion(ctx context.Context, discussionID uuid.UUID) error
GetLetterApprovalInfo(ctx context.Context, letterID uuid.UUID) (*contract.LetterApprovalInfoResponse, error)
GetApprovalDiscussions(ctx context.Context, letterID uuid.UUID) (*contract.OutgoingLetterApprovalDiscussionsResponse, error)
}
type LetterOutgoingHandler struct {
@@ -420,4 +424,41 @@ func (h *LetterOutgoingHandler) DeleteDiscussion(c *gin.Context) {
}
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "discussion deleted"})
}
func (h *LetterOutgoingHandler) GetLetterApprovalInfo(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest})
return
}
resp, err := h.svc.GetLetterApprovalInfo(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return
}
c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp))
}
// GetApprovalDiscussions returns both approvals and discussions for an outgoing letter
func (h *LetterOutgoingHandler) GetApprovalDiscussions(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{Error: "invalid id", Code: http.StatusBadRequest})
return
}
resp, err := h.svc.GetApprovalDiscussions(c.Request.Context(), id)
if err != nil {
if err == gorm.ErrRecordNotFound {
c.JSON(http.StatusNotFound, &contract.ErrorResponse{Error: "letter not found", Code: http.StatusNotFound})
return
}
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{Error: err.Error(), Code: http.StatusInternalServerError})
return
}
c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp))
}
+205
View File
@@ -0,0 +1,205 @@
package handler
import (
"context"
"eslogad-be/internal/contract"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type OnlyOfficeService interface {
ProcessCallback(ctx context.Context, documentKey string, req *contract.OnlyOfficeCallbackRequest) (*contract.OnlyOfficeCallbackResponse, error)
GetEditorConfig(ctx context.Context, req *contract.GetEditorConfigRequest) (*contract.GetEditorConfigResponse, error)
LockDocument(ctx context.Context, documentID uuid.UUID, userID uuid.UUID) error
UnlockDocument(ctx context.Context, documentID uuid.UUID, userID uuid.UUID) error
GetDocumentSession(ctx context.Context, documentKey string) (*contract.DocumentSession, error)
GetOnlyOfficeConfig(ctx context.Context) (*contract.OnlyOfficeConfigInfo, error)
}
type OnlyOfficeHandler struct {
svc OnlyOfficeService
}
func NewOnlyOfficeHandler(svc OnlyOfficeService) *OnlyOfficeHandler {
return &OnlyOfficeHandler{
svc: svc,
}
}
// ProcessCallback handles OnlyOffice document server callbacks
// POST /api/v1/onlyoffice/callback/:key
func (h *OnlyOfficeHandler) ProcessCallback(c *gin.Context) {
documentKey := c.Param("key")
if documentKey == "" {
c.JSON(http.StatusBadRequest, &contract.OnlyOfficeCallbackResponse{Error: 1})
return
}
var req contract.OnlyOfficeCallbackRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, &contract.OnlyOfficeCallbackResponse{Error: 2})
return
}
// Extract JWT token from Authorization header if not in request body
if req.Token == "" {
authHeader := c.GetHeader("Authorization")
if authHeader != "" {
// Remove "Bearer " prefix if present
if len(authHeader) > 7 && authHeader[:7] == "Bearer " {
req.Token = authHeader[7:]
} else {
req.Token = authHeader
}
}
}
// OnlyOffice requires the key in the request to match the URL
if req.Key != "" && req.Key != documentKey {
c.JSON(http.StatusBadRequest, &contract.OnlyOfficeCallbackResponse{Error: 1})
return
}
req.Key = documentKey
resp, err := h.svc.ProcessCallback(c.Request.Context(), documentKey, &req)
if err != nil {
// Log the error for debugging but return appropriate OnlyOffice error code
// OnlyOffice expects specific error codes, not standard HTTP errors
c.JSON(http.StatusOK, &contract.OnlyOfficeCallbackResponse{Error: 0})
return
}
// OnlyOffice expects 200 OK with error field in response
c.JSON(http.StatusOK, resp)
}
func (h *OnlyOfficeHandler) GetEditorConfig(c *gin.Context) {
var req contract.GetEditorConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{
Error: fmt.Sprintf("invalid request body: %v", err),
Code: http.StatusBadRequest,
})
return
}
resp, err := h.svc.GetEditorConfig(c.Request.Context(), &req)
if err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{
Error: err.Error(),
Code: http.StatusInternalServerError,
})
return
}
c.JSON(http.StatusOK, contract.BuildSuccessResponse(resp))
}
// LockDocument locks a document for editing
// POST /api/v1/onlyoffice/lock/:id
func (h *OnlyOfficeHandler) LockDocument(c *gin.Context) {
documentID, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{
Error: "invalid document id",
Code: http.StatusBadRequest,
})
return
}
// Get user ID from context
userCtx := c.MustGet("user").(map[string]interface{})
userID, err := uuid.Parse(userCtx["user_id"].(string))
if err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{
Error: "invalid user context",
Code: http.StatusBadRequest,
})
return
}
if err := h.svc.LockDocument(c.Request.Context(), documentID, userID); err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{
Error: err.Error(),
Code: http.StatusInternalServerError,
})
return
}
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "document locked"})
}
// UnlockDocument unlocks a document
// POST /api/v1/onlyoffice/unlock/:id
func (h *OnlyOfficeHandler) UnlockDocument(c *gin.Context) {
documentID, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{
Error: "invalid document id",
Code: http.StatusBadRequest,
})
return
}
// Get user ID from context
userCtx := c.MustGet("user").(map[string]interface{})
userID, err := uuid.Parse(userCtx["user_id"].(string))
if err != nil {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{
Error: "invalid user context",
Code: http.StatusBadRequest,
})
return
}
if err := h.svc.UnlockDocument(c.Request.Context(), documentID, userID); err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{
Error: err.Error(),
Code: http.StatusInternalServerError,
})
return
}
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "document unlocked"})
}
// GetDocumentSession gets document session information
// GET /api/v1/onlyoffice/session/:key
func (h *OnlyOfficeHandler) GetDocumentSession(c *gin.Context) {
documentKey := c.Param("key")
if documentKey == "" {
c.JSON(http.StatusBadRequest, &contract.ErrorResponse{
Error: "document key is required",
Code: http.StatusBadRequest,
})
return
}
session, err := h.svc.GetDocumentSession(c.Request.Context(), documentKey)
if err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{
Error: err.Error(),
Code: http.StatusInternalServerError,
})
return
}
c.JSON(http.StatusOK, contract.BuildSuccessResponse(session))
}
// GetOnlyOfficeConfig returns the OnlyOffice configuration
// GET /api/v1/onlyoffice/config
func (h *OnlyOfficeHandler) GetOnlyOfficeConfig(c *gin.Context) {
config, err := h.svc.GetOnlyOfficeConfig(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, &contract.ErrorResponse{
Error: err.Error(),
Code: http.StatusInternalServerError,
})
return
}
c.JSON(http.StatusOK, contract.BuildSuccessResponse(config))
}