repository attachment api

This commit is contained in:
efrilm 2025-10-15 21:58:44 +07:00
parent da2246d45a
commit 58128495a6
15 changed files with 606 additions and 64 deletions

View File

@ -53,6 +53,7 @@ func (a *App) Initialize(cfg *config.Config) error {
onlyOfficeHandler := handler.NewOnlyOfficeHandler(services.onlyOfficeService)
analyticsHandler := handler.NewAnalyticsHandler(services.analyticsService)
notificationHandler := handler.NewNotificationHandler(services.notificationService)
repositoryAttachmentHandler := handler.NewRepositoryAttachmentHandler(services.repositoryAttachmentService)
a.router = router.NewRouter(
cfg,
@ -70,6 +71,7 @@ func (a *App) Initialize(cfg *config.Config) error {
onlyOfficeHandler,
analyticsHandler,
notificationHandler,
repositoryAttachmentHandler,
)
return nil
@ -147,6 +149,7 @@ type repositories struct {
approvalFlowRepo *repository.ApprovalFlowRepository
letterOutgoingApprovalRepo *repository.LetterOutgoingApprovalRepository
analyticsRepo *repository.AnalyticsRepository
repositoryAttachmentRepo *repository.RepositoryAttachmentRepositoryImpl
}
func (a *App) initRepositories() *repositories {
@ -181,6 +184,7 @@ func (a *App) initRepositories() *repositories {
approvalFlowRepo: repository.NewApprovalFlowRepository(a.db),
letterOutgoingApprovalRepo: repository.NewLetterOutgoingApprovalRepository(a.db),
analyticsRepo: repository.NewAnalyticsRepository(a.db),
repositoryAttachmentRepo: repository.NewRepositoryAttachmentRepositoryImpl(a.db),
}
}
@ -198,13 +202,14 @@ type processors struct {
letterDispositionProcessor *processor.LetterDispositionProcessorImpl
letterDispositionDeptProcessor *processor.LetterDispositionDepartmentProcessorImpl
// Modular processors for letter outgoing
letterValidationProcessor processor.LetterValidationProcessor
letterCreationProcessor processor.LetterCreationProcessor
letterApprovalProcessor processor.LetterApprovalProcessor
letterAttachmentProcessor processor.LetterAttachmentProcessor
letterOutgoingRecipientProcessor processor.LetterOutgoingRecipientProcessor
letterActivityProcessor processor.LetterActivityProcessor
txManager *repository.TxManager
letterValidationProcessor processor.LetterValidationProcessor
letterCreationProcessor processor.LetterCreationProcessor
letterApprovalProcessor processor.LetterApprovalProcessor
letterAttachmentProcessor processor.LetterAttachmentProcessor
letterOutgoingRecipientProcessor processor.LetterOutgoingRecipientProcessor
letterActivityProcessor processor.LetterActivityProcessor
repositoryAttachmentProcessor *processor.RepositoryAttachmentProcessorImpl
txManager *repository.TxManager
}
func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processors {
@ -327,6 +332,9 @@ func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processor
repos.letterRepo,
)
repositoryAttachmentProc := processor.NewRepositoryAttachmentProcessor(
repos.repositoryAttachmentRepo)
return &processors{
userProcessor: userProc,
cachedUserProcessor: cachedUserProc,
@ -347,23 +355,25 @@ func (a *App) initProcessors(cfg *config.Config, repos *repositories) *processor
letterAttachmentProcessor: letterAttachmentProc,
letterOutgoingRecipientProcessor: letterOutgoingRecipientProc,
letterActivityProcessor: letterActivityProc,
repositoryAttachmentProcessor: repositoryAttachmentProc,
txManager: txMgr,
}
}
type services struct {
userService *service.UserServiceImpl
authService *service.AuthServiceImpl
fileService *service.FileServiceImpl
rbacService *service.RBACServiceImpl
masterService *service.MasterServiceImpl
letterService *service.LetterServiceImpl
letterOutgoingService *service.LetterOutgoingServiceImpl
approvalFlowService *service.ApprovalFlowServiceImpl
dispositionRouteService *service.DispositionRouteServiceImpl
onlyOfficeService *service.OnlyOfficeServiceImpl
analyticsService *service.AnalyticsServiceImpl
notificationService *service.NotificationServiceImpl
userService *service.UserServiceImpl
authService *service.AuthServiceImpl
fileService *service.FileServiceImpl
rbacService *service.RBACServiceImpl
masterService *service.MasterServiceImpl
letterService *service.LetterServiceImpl
letterOutgoingService *service.LetterOutgoingServiceImpl
approvalFlowService *service.ApprovalFlowServiceImpl
dispositionRouteService *service.DispositionRouteServiceImpl
onlyOfficeService *service.OnlyOfficeServiceImpl
analyticsService *service.AnalyticsServiceImpl
notificationService *service.NotificationServiceImpl
repositoryAttachmentService *service.RepositoryAttachmentServiceImpl
}
func (a *App) initServices(processors *processors, repos *repositories, cfg *config.Config) *services {
@ -423,19 +433,22 @@ func (a *App) initServices(processors *processors, repos *repositories, cfg *con
novuConfig := internalConfig.LoadNovuConfig(cfg)
notificationSvc := service.NewNotificationService(novuConfig, processors.userProcessor)
repositoryAttachmentSvc := service.NewRepositoryAttachmentService(processors.repositoryAttachmentProcessor)
return &services{
userService: userSvc,
authService: authService,
fileService: fileSvc,
rbacService: rbacSvc,
masterService: masterSvc,
letterService: letterSvc,
letterOutgoingService: letterOutgoingSvc,
approvalFlowService: approvalFlowSvc,
dispositionRouteService: dispRouteSvc,
onlyOfficeService: onlyOfficeSvc,
analyticsService: analyticsSvc,
notificationService: notificationSvc,
userService: userSvc,
authService: authService,
fileService: fileSvc,
rbacService: rbacSvc,
masterService: masterSvc,
letterService: letterSvc,
letterOutgoingService: letterOutgoingSvc,
approvalFlowService: approvalFlowSvc,
dispositionRouteService: dispRouteSvc,
onlyOfficeService: onlyOfficeSvc,
analyticsService: analyticsSvc,
notificationService: notificationSvc,
repositoryAttachmentService: repositoryAttachmentSvc,
}
}

View File

@ -0,0 +1,35 @@
package contract
import (
"time"
"github.com/google/uuid"
)
type CreateRepositoryAttachmentRequest struct {
FileURL string `json:"file_url" validate:"required"`
FileName string `json:"file_name" validate:"required"`
FileType string `json:"file_type" validate:"required"`
Category string `json:"category" validate:"required"`
}
type RepositoryAttachmentsResponse struct {
ID uuid.UUID `json:"id"`
FileURL string `json:"file_url"`
FileName string `json:"file_name"`
FileType string `json:"file_type"`
Category string `json:"category"`
UploadBy uuid.UUID `json:"upload_by"`
UploadAt time.Time `json:"upload_at"`
}
type ListRepositoryAttachmentsResponse struct {
Attachments []RepositoryAttachmentsResponse `json:"attachments"`
Pagination PaginationResponse `json:"pagination"`
}
type ListRepositoryAttachmentsRequest struct {
Page int `json:"page"`
Limit int `json:"limit"`
Search *string `json:"search,omitempty"`
}

View File

@ -0,0 +1,19 @@
package entities
import (
"time"
"github.com/google/uuid"
)
type RepositoryAttachment struct {
ID uuid.UUID `gorm:"type:uuid;primary_key;default:gen_random_uuid()" json:"id"`
FileURL string `gorm:"not null" json:"file_url"`
FileName string `gorm:"not null" json:"file_name"`
FileType string `gorm:"not null" json:"file_type"`
Category string `gorm:"not null" json:"category"`
UploadedBy *uuid.UUID `json:"uploaded_by,omitempty"`
UploadedAt time.Time `gorm:"autoCreateTime" json:"uploaded_at"`
}
func (RepositoryAttachment) TableName() string { return "repository_attachments" }

View File

@ -0,0 +1,137 @@
package handler
import (
"eslogad-be/internal/constants"
"eslogad-be/internal/contract"
"eslogad-be/internal/logger"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type RepositoryAttachmentHandler struct {
attachmentService RepositoryAttachmentService
}
func NewRepositoryAttachmentHandler(attachmentService RepositoryAttachmentService) *RepositoryAttachmentHandler {
return &RepositoryAttachmentHandler{
attachmentService: attachmentService,
}
}
func (h *RepositoryAttachmentHandler) CreateAttachment(c *gin.Context) {
var req contract.CreateRepositoryAttachmentRequest
if err := c.ShouldBindJSON(&req); err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::CreateAttachment -> request binding failed")
h.sendValidationErrorResponse(c, "Invalid request body", constants.MissingFieldErrorCode)
return
}
userResponse, err := h.attachmentService.CreateAttachment(c.Request.Context(), &req)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::CreateAttachment -> Failed to create user from service")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
logger.FromContext(c).Infof("UserHandler::CreateUser -> Successfully created repository attachment = %+v", userResponse)
c.JSON(http.StatusOK, contract.BuildSuccessResponse(userResponse))
}
func (h *RepositoryAttachmentHandler) DeleteAttachment(c *gin.Context) {
attachmentIDStr := c.Param("id")
attachmentID, err := uuid.Parse(attachmentIDStr)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::DeleteAttachment -> Invalid attachment id")
h.sendValidationErrorResponse(c, "Invalid user ID", constants.MalformedFieldErrorCode)
return
}
err = h.attachmentService.DeleteAttachment(c.Request.Context(), attachmentID)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::DeleteAttachment -> Failed to delete attachment from service")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
logger.FromContext(c).Info("UserHandler::DeleteAttachment -> Successfully deleted attachment")
c.JSON(http.StatusOK, &contract.SuccessResponse{Message: "User deleted successfully"})
}
func (h *RepositoryAttachmentHandler) GetAttachment(c *gin.Context) {
attachmentIDStr := c.Param("id")
attachmentID, err := uuid.Parse(attachmentIDStr)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::GetAttachment -> Invalid attachment ID")
h.sendValidationErrorResponse(c, "Invalid user ID", constants.MalformedFieldErrorCode)
return
}
attachmentResponse, err := h.attachmentService.GetById(c.Request.Context(), attachmentID)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::GetAttachment -> Failed to get attachment from service")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
logger.FromContext(c).Infof("UserHandler::GetAttachment -> Successfully retrieved attachment = %+v", attachmentResponse)
c.JSON(http.StatusOK, attachmentResponse)
}
func (h *RepositoryAttachmentHandler) ListAttachment(c *gin.Context) {
ctx := c.Request.Context()
req := &contract.ListRepositoryAttachmentsRequest{
Page: 1,
Limit: 10,
}
if page := c.Query("page"); page != "" {
if p, err := strconv.Atoi(page); err == nil {
req.Page = p
}
}
if limit := c.Query("limit"); limit != "" {
if l, err := strconv.Atoi(limit); err == nil {
req.Limit = l
}
}
attachmentsResponse, err := h.attachmentService.ListAttachment(ctx, req)
if err != nil {
logger.FromContext(c).WithError(err).Error("UserHandler::ListUsers -> Failed to list users from service")
h.sendErrorResponse(c, err.Error(), http.StatusInternalServerError)
return
}
logger.FromContext(c).Infof("UserHandler::ListUsers -> Successfully listed users = %+v", attachmentsResponse)
c.JSON(http.StatusOK, contract.BuildSuccessResponse(attachmentsResponse))
}
func (h *RepositoryAttachmentHandler) sendValidationErrorResponse(c *gin.Context, message string, errorCode string) {
statusCode := constants.HttpErrorMap[errorCode]
if statusCode == 0 {
statusCode = http.StatusBadRequest
}
errorResponse := &contract.ErrorResponse{
Error: message,
Code: statusCode,
Details: map[string]interface{}{
"error_code": errorCode,
"entity": constants.UserValidatorEntity,
},
}
c.JSON(statusCode, errorResponse)
}
func (h *RepositoryAttachmentHandler) sendErrorResponse(c *gin.Context, message string, statusCode int) {
errorResponse := &contract.ErrorResponse{
Error: message,
Code: statusCode,
Details: map[string]interface{}{},
}
c.JSON(statusCode, errorResponse)
}

View File

@ -0,0 +1,15 @@
package handler
import (
"context"
"eslogad-be/internal/contract"
"github.com/google/uuid"
)
type RepositoryAttachmentService interface {
CreateAttachment(ctx context.Context, req *contract.CreateRepositoryAttachmentRequest) (*contract.RepositoryAttachmentsResponse, error)
DeleteAttachment(ctx context.Context, id uuid.UUID) error
GetById(ctx context.Context, id uuid.UUID) (*contract.RepositoryAttachmentsResponse, error)
ListAttachment(ctx context.Context, req *contract.ListRepositoryAttachmentsRequest) (*contract.ListRepositoryAttachmentsResponse, error)
}

View File

@ -0,0 +1,78 @@
package processor
import (
"context"
"eslogad-be/internal/appcontext"
"eslogad-be/internal/contract"
"eslogad-be/internal/transformer"
"fmt"
"github.com/google/uuid"
)
type RepositoryAttachmentProcessorImpl struct {
attachmentRepo RepositoryAttachmentRepository
}
func NewRepositoryAttachmentProcessor(attachmentRepo RepositoryAttachmentRepository) *RepositoryAttachmentProcessorImpl {
return &RepositoryAttachmentProcessorImpl{
attachmentRepo: attachmentRepo,
}
}
func (p *RepositoryAttachmentProcessorImpl) CreateAttachment(ctx context.Context, req *contract.CreateRepositoryAttachmentRequest) (*contract.RepositoryAttachmentsResponse, error) {
userID := getUserIDFromContext(ctx)
attachmentEntity := transformer.CreateRepositoryAttachmentRequestToEntity(req, userID)
err := p.attachmentRepo.Create(ctx, attachmentEntity)
if err != nil {
return nil, fmt.Errorf("failed to create repository attachment: %w", err)
}
return transformer.RepositoryAttachmentEntityToContract(attachmentEntity), nil
}
func getUserIDFromContext(ctx context.Context) uuid.UUID {
appCtx := appcontext.FromGinContext(ctx)
if appCtx != nil {
return appCtx.UserID
}
return uuid.New()
}
func (p *RepositoryAttachmentProcessorImpl) DeleteAttachment(ctx context.Context, id uuid.UUID) error {
_, err := p.attachmentRepo.GetByID(ctx, id)
if err != nil {
return fmt.Errorf("repository attachment not found: %w", err)
}
err = p.attachmentRepo.Delete(ctx, id)
if err != nil {
return fmt.Errorf("failed to delete repository attachment: %w", err)
}
return nil
}
func (p *RepositoryAttachmentProcessorImpl) GetById(ctx context.Context, id uuid.UUID) (*contract.RepositoryAttachmentsResponse, error) {
attachment, err := p.attachmentRepo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("repository attachment not found: %w", err)
}
resp := transformer.RepositoryAttachmentEntityToContract(attachment)
return resp, nil
}
func (p *RepositoryAttachmentProcessorImpl) ListAttachment(ctx context.Context, search *string, limit, offset int) ([]contract.RepositoryAttachmentsResponse, int, error) {
attachments, totalCount, err := p.attachmentRepo.List(ctx, search, limit, offset)
if err != nil {
return nil, 0, fmt.Errorf("failed to get users: %w", err)
}
responses := transformer.RepositoryAttachmentEntityToContracts(attachments)
return responses, int(totalCount), nil
}

View File

@ -0,0 +1,16 @@
package processor
import (
"context"
"eslogad-be/internal/entities"
"github.com/google/uuid"
)
type RepositoryAttachmentRepository interface {
Create(ctx context.Context, user *entities.RepositoryAttachment) error
GetByID(ctx context.Context, id uuid.UUID) (*entities.RepositoryAttachment, error)
Update(ctx context.Context, user *entities.RepositoryAttachment) error
Delete(ctx context.Context, id uuid.UUID) error
List(ctx context.Context, search *string, limit, offset int) ([]*entities.RepositoryAttachment, int64, error)
}

View File

@ -0,0 +1,74 @@
package repository
import (
"context"
"eslogad-be/internal/entities"
"github.com/google/uuid"
"gorm.io/gorm"
)
type RepositoryAttachmentRepositoryImpl struct {
b *gorm.DB
}
func NewRepositoryAttachmentRepositoryImpl(db *gorm.DB) *RepositoryAttachmentRepositoryImpl {
return &RepositoryAttachmentRepositoryImpl{
b: db,
}
}
func (r *RepositoryAttachmentRepositoryImpl) Create(ctx context.Context, user *entities.RepositoryAttachment) error {
return r.b.WithContext(ctx).Create(user).Error
}
func (r *RepositoryAttachmentRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entities.RepositoryAttachment, error) {
var attachment entities.RepositoryAttachment
err := r.b.WithContext(ctx).
First(&attachment, "id = ?", id).Error
if err != nil {
return nil, err
}
return &attachment, nil
}
func (r *RepositoryAttachmentRepositoryImpl) Update(ctx context.Context, user *entities.RepositoryAttachment) error {
return r.b.WithContext(ctx).Save(user).Error
}
func (r *RepositoryAttachmentRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error {
return r.b.WithContext(ctx).Delete(&entities.RepositoryAttachment{}, "id = ?", id).Error
}
func (r *RepositoryAttachmentRepositoryImpl) List(ctx context.Context, search *string, limit, offset int) ([]*entities.RepositoryAttachment, int64, error) {
var attachments []*entities.RepositoryAttachment
var total int64
baseQuery := r.b.WithContext(ctx).Model(&entities.RepositoryAttachment{})
if search != nil && *search != "" {
like := "%" + *search + "%"
baseQuery = baseQuery.Where("name ILIKE ? OR email ILIKE ?", like, like)
}
countQuery := baseQuery
if err := countQuery.Count(&total).Error; err != nil {
return nil, 0, err
}
dataQuery := r.b.WithContext(ctx).Model(&entities.RepositoryAttachment{})
if search != nil && *search != "" {
like := "%" + *search + "%"
dataQuery = dataQuery.Where("name ILIKE ? OR category ILIKE ?", like, like)
}
if err := dataQuery.
Limit(limit).
Offset(offset).
Find(&attachments).Error; err != nil {
return nil, 0, err
}
return attachments, total, nil
}

View File

@ -180,3 +180,10 @@ type NotificationHandler interface {
GetCurrentUserSubscriber(c *gin.Context)
UpdateCurrentUserSubscriberChannel(c *gin.Context)
}
type RepositoryAttachmentHandler interface {
CreateAttachment(c *gin.Context)
DeleteAttachment(c *gin.Context)
GetAttachment(c *gin.Context)
ListAttachment(c *gin.Context)
}

View File

@ -8,21 +8,22 @@ import (
)
type Router struct {
config *config.Config
authHandler AuthHandler
healthHandler HealthHandler
authMiddleware AuthMiddleware
userHandler UserHandler
fileHandler FileHandler
rbacHandler RBACHandler
masterHandler MasterHandler
letterHandler LetterHandler
letterOutgoingHandler LetterOutgoingHandler
adminApprovalFlowHandler AdminApprovalFlowHandler
dispRouteHandler DispositionRouteHandler
onlyOfficeHandler OnlyOfficeHandler
analyticsHandler AnalyticsHandler
notificationHandler NotificationHandler
config *config.Config
authHandler AuthHandler
healthHandler HealthHandler
authMiddleware AuthMiddleware
userHandler UserHandler
fileHandler FileHandler
rbacHandler RBACHandler
masterHandler MasterHandler
letterHandler LetterHandler
letterOutgoingHandler LetterOutgoingHandler
adminApprovalFlowHandler AdminApprovalFlowHandler
dispRouteHandler DispositionRouteHandler
onlyOfficeHandler OnlyOfficeHandler
analyticsHandler AnalyticsHandler
notificationHandler NotificationHandler
repositoryAttachmentHandler RepositoryAttachmentHandler
}
func NewRouter(
@ -41,23 +42,25 @@ func NewRouter(
onlyOfficeHandler OnlyOfficeHandler,
analyticsHandler AnalyticsHandler,
notificationHandler NotificationHandler,
repositoryAttachmentHandler RepositoryAttachmentHandler,
) *Router {
return &Router{
config: cfg,
authHandler: authHandler,
authMiddleware: authMiddleware,
healthHandler: healthHandler,
userHandler: userHandler,
fileHandler: fileHandler,
rbacHandler: rbacHandler,
masterHandler: masterHandler,
letterHandler: letterHandler,
letterOutgoingHandler: letterOutgoingHandler,
adminApprovalFlowHandler: adminApprovalFlowHandler,
dispRouteHandler: dispRouteHandler,
onlyOfficeHandler: onlyOfficeHandler,
analyticsHandler: analyticsHandler,
notificationHandler: notificationHandler,
config: cfg,
authHandler: authHandler,
authMiddleware: authMiddleware,
healthHandler: healthHandler,
userHandler: userHandler,
fileHandler: fileHandler,
rbacHandler: rbacHandler,
masterHandler: masterHandler,
letterHandler: letterHandler,
letterOutgoingHandler: letterOutgoingHandler,
adminApprovalFlowHandler: adminApprovalFlowHandler,
dispRouteHandler: dispRouteHandler,
onlyOfficeHandler: onlyOfficeHandler,
analyticsHandler: analyticsHandler,
notificationHandler: notificationHandler,
repositoryAttachmentHandler: repositoryAttachmentHandler,
}
}
@ -236,6 +239,15 @@ func (r *Router) addAppRoutes(rg *gin.Engine) {
droutes.PUT("/:id/active", r.dispRouteHandler.SetActive)
}
repoattachsch := v1.Group("/repository-attachments")
repoattachsch.Use(r.authMiddleware.RequireAuth())
{
repoattachsch.POST("", r.repositoryAttachmentHandler.CreateAttachment)
repoattachsch.GET("", r.repositoryAttachmentHandler.ListAttachment)
repoattachsch.DELETE("/:id", r.repositoryAttachmentHandler.DeleteAttachment)
repoattachsch.GET("/:id", r.repositoryAttachmentHandler.GetAttachment)
}
admin := v1.Group("/setting")
admin.Use(r.authMiddleware.RequireAuth())
{

View File

@ -0,0 +1,15 @@
package service
import (
"context"
"eslogad-be/internal/contract"
"github.com/google/uuid"
)
type RepositoryAttachmentProcessor interface {
CreateAttachment(ctx context.Context, req *contract.CreateRepositoryAttachmentRequest) (*contract.RepositoryAttachmentsResponse, error)
DeleteAttachment(ctx context.Context, id uuid.UUID) error
GetById(ctx context.Context, id uuid.UUID) (*contract.RepositoryAttachmentsResponse, error)
ListAttachment(ctx context.Context, search *string, limit, offset int) ([]contract.RepositoryAttachmentsResponse, int, error)
}

View File

@ -0,0 +1,59 @@
package service
import (
"context"
"eslogad-be/internal/contract"
"eslogad-be/internal/transformer"
"github.com/google/uuid"
)
type RepositoryAttachmentServiceImpl struct {
attachmentProcessor RepositoryAttachmentProcessor
}
func NewRepositoryAttachmentService(attachmentProcessor RepositoryAttachmentProcessor) *RepositoryAttachmentServiceImpl {
return &RepositoryAttachmentServiceImpl{
attachmentProcessor: attachmentProcessor,
}
}
func (s *RepositoryAttachmentServiceImpl) CreateAttachment(ctx context.Context, req *contract.CreateRepositoryAttachmentRequest) (*contract.RepositoryAttachmentsResponse, error) {
return s.attachmentProcessor.CreateAttachment(ctx, req)
}
func (s *RepositoryAttachmentServiceImpl) DeleteAttachment(ctx context.Context, id uuid.UUID) error {
return s.attachmentProcessor.DeleteAttachment(ctx, id)
}
func (s *RepositoryAttachmentServiceImpl) GetById(ctx context.Context, id uuid.UUID) (*contract.RepositoryAttachmentsResponse, error) {
return s.attachmentProcessor.GetById(ctx, id)
}
func (s *RepositoryAttachmentServiceImpl) ListAttachment(ctx context.Context, req *contract.ListRepositoryAttachmentsRequest) (*contract.ListRepositoryAttachmentsResponse, error) {
page := req.Page
if page <= 0 {
page = 1
}
limit := req.Limit
if limit <= 0 {
limit = 10
}
if limit > 100 {
limit = 100 // Max limit to prevent performance issues
}
offset := (page - 1) * limit
// Pass calculated offset and limit to processor
attachmentResponses, totalCount, err := s.attachmentProcessor.ListAttachment(ctx, req.Search, limit, offset)
if err != nil {
return nil, err
}
return &contract.ListRepositoryAttachmentsResponse{
Attachments: attachmentResponses,
Pagination: transformer.CreatePaginationResponse(totalCount, page, limit),
}, nil
}

View File

@ -0,0 +1,49 @@
package transformer
import (
"eslogad-be/internal/contract"
"eslogad-be/internal/entities"
"github.com/google/uuid"
)
func CreateRepositoryAttachmentRequestToEntity(req *contract.CreateRepositoryAttachmentRequest, userId uuid.UUID) *entities.RepositoryAttachment {
if req == nil {
return nil
}
return &entities.RepositoryAttachment{
FileName: req.FileName,
FileType: req.FileType,
FileURL: req.FileURL,
UploadedBy: &userId,
Category: req.Category,
}
}
func RepositoryAttachmentEntityToContract(entity *entities.RepositoryAttachment) *contract.RepositoryAttachmentsResponse {
resp := &contract.RepositoryAttachmentsResponse{
ID: entity.ID,
FileName: entity.FileName,
FileType: entity.FileType,
FileURL: entity.FileURL,
Category: entity.Category,
UploadBy: *entity.UploadedBy,
UploadAt: entity.UploadedAt,
}
return resp
}
func RepositoryAttachmentEntityToContracts(attachments []*entities.RepositoryAttachment) []contract.RepositoryAttachmentsResponse {
if attachments == nil {
return nil
}
responses := make([]contract.RepositoryAttachmentsResponse, len(attachments))
for i, u := range attachments {
resp := RepositoryAttachmentEntityToContract(u)
if resp != nil {
responses[i] = *resp
}
}
return responses
}

View File

@ -0,0 +1,13 @@
BEGIN;
CREATE TABLE IF NOT EXISTS repository_attachments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
file_url TEXT NOT NULL,
file_name TEXT NOT NULL,
file_type TEXT NOT NULL,
category TEXT NOT NULL,
uploaded_by UUID REFERENCES users(id) ON DELETE SET NULL,
uploaded_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
COMMIT;