Added APIs for delete and get all documents
This commit is contained in:
@@ -2,6 +2,9 @@ package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fileserver/config"
|
||||
"fileserver/internal/models"
|
||||
"fileserver/internal/service"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
@@ -18,6 +21,37 @@ const (
|
||||
localFolderTemplate = "%s/fileserver/uploads/" // Template for creating local upload directories
|
||||
)
|
||||
|
||||
// GetFiles retrieves the list of indexed documents from the database with fuzzy search on file names
|
||||
func GetFiles(w http.ResponseWriter, r *http.Request) {
|
||||
// Step 1: Retrieve the search query from the URL parameters
|
||||
searchQuery := r.URL.Query().Get("searchQuery")
|
||||
if searchQuery == "" {
|
||||
// If there is no search query, retrieve all documents
|
||||
searchQuery = "%"
|
||||
} else {
|
||||
// Add wildcards for partial search
|
||||
searchQuery = "%" + searchQuery + "%"
|
||||
}
|
||||
|
||||
// Step 2: Retrieve documents whose name matches the fuzzy search
|
||||
documents, err := service.GetFiles(searchQuery)
|
||||
if err != nil {
|
||||
// Handle error if the query fails
|
||||
http.Error(w, fmt.Sprintf("Error retrieving documents: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Step 3: Convert the documents to JSON format
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
// Use json.NewEncoder to write the response directly in JSON format
|
||||
if err := json.NewEncoder(w).Encode(documents); err != nil {
|
||||
http.Error(w, fmt.Sprintf("Error encoding response: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// GetFile handles the request to fetch a file from MinIO and serve it to the user.
|
||||
func GetFile(w http.ResponseWriter, r *http.Request) {
|
||||
// Ensure that the request method is GET
|
||||
@@ -27,7 +61,19 @@ func GetFile(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Extract the object name from the query parameters
|
||||
objectName := r.URL.Query().Get("file")
|
||||
objectName := r.PathValue("idFile")
|
||||
idFile, err := uuid.Parse(objectName)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Error parsing objectName: %v", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
document, err := service.GetDocument(idFile)
|
||||
if document == nil || err != nil {
|
||||
http.Error(w, fmt.Sprintf("Error retrieving document: %v", err), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch the file object from MinIO storage
|
||||
object, err := service.GetFileFromMinIO(defaultBucketName, objectName)
|
||||
if err != nil {
|
||||
@@ -121,7 +167,8 @@ func LoadFile(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Use a unique file name based on timestamp and the original file name
|
||||
newFileName := fmt.Sprintf("%d_%s", time.Now().Unix(), header.Filename)
|
||||
out, err := os.Create(uploadDir + newFileName)
|
||||
filePath := uploadDir + newFileName
|
||||
out, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
http.Error(w, "Error saving the file: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -143,11 +190,37 @@ func LoadFile(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate fingerprint of file
|
||||
//fingerprint, err := utils.CalculateFingerprint(filePath)
|
||||
//if err != nil {
|
||||
// http.Error(w, "Error during calculate fingerprint: "+err.Error(), http.StatusInternalServerError)
|
||||
// return
|
||||
//}
|
||||
fingerprint := uuid.New().String()
|
||||
|
||||
// Check if document already uploaded
|
||||
_, err = service.GetDocumentByFingerprint(fingerprint)
|
||||
if err == nil {
|
||||
http.Error(w, "Document already exists.", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
|
||||
// Upload the file to MinIO with a unique ID (UUID)
|
||||
idFile := uuid.New().String()
|
||||
err = service.UploadFileToMinIO(context.Background(), defaultBucketName, idFile, uploadDir+newFileName)
|
||||
idFile := uuid.New()
|
||||
err = service.UploadFileToMinIO(context.Background(), defaultBucketName, idFile.String(), filePath)
|
||||
if err != nil {
|
||||
http.Error(w, "", http.StatusInternalServerError)
|
||||
http.Error(w, "Error during upload file to MinIO: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Save document to database
|
||||
newDocument := &models.Document{
|
||||
Name: header.Filename,
|
||||
IdFile: idFile,
|
||||
Fingerprint: fingerprint,
|
||||
}
|
||||
if err := service.AddDocument(newDocument); err != nil {
|
||||
http.Error(w, "Error adding document: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -175,7 +248,35 @@ func cleanup(file *os.File) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteFile handles the file deletion logic (currently empty).
|
||||
// DeleteFile deletes a file from the database and MinIO
|
||||
func DeleteFile(w http.ResponseWriter, r *http.Request) {
|
||||
// This function is a placeholder for file deletion logic.
|
||||
// Ensure that the request method is GET
|
||||
if r.Method != http.MethodDelete {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the object name from the path value
|
||||
idFile, err := uuid.Parse(r.PathValue("idFile"))
|
||||
if err != nil {
|
||||
http.Error(w, "Error parsing the idFile: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Step 1: Get document from PostgreSQL database
|
||||
document, err := service.GetDocument(idFile)
|
||||
if err != nil {
|
||||
http.Error(w, "Document not found: "+err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// Step 2: Delete from PostgreSQL
|
||||
if err := config.DB.Delete(&document).Error; err != nil {
|
||||
http.Error(w, fmt.Sprintf("Error deleting document from DB: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Return success response
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "File with ID %v deleted successfully", idFile)
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ package api
|
||||
import "net/http"
|
||||
|
||||
var Routes = map[string]func(w http.ResponseWriter, r *http.Request){
|
||||
"GET /": Hello,
|
||||
"GET /file": GetFile,
|
||||
"POST /file": LoadFile,
|
||||
"DELETE /file": DeleteFile,
|
||||
"GET /": Hello,
|
||||
"GET /files": GetFiles,
|
||||
"GET /file/{idFile}": GetFile,
|
||||
"POST /file": LoadFile,
|
||||
"DELETE /file/{idFile}": DeleteFile,
|
||||
}
|
||||
|
||||
@@ -9,13 +9,48 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GetFiles retrieves a list of documents from the database based on a fuzzy search on file names.
|
||||
// It only returns documents that have not been logically deleted (i.e., deleted_at is NULL).
|
||||
// The function performs a case-insensitive search using the provided search query.
|
||||
//
|
||||
// Parameters:
|
||||
// - searchQuery (string): The search term used to find documents by their file name. This will be used
|
||||
// in a fuzzy search with the 'ILIKE' operator in PostgreSQL.
|
||||
//
|
||||
// Returns:
|
||||
// - []models.Document: A slice of documents that match the search query and are not logically deleted.
|
||||
// - error: An error is returned if there is an issue with retrieving the documents from the database.
|
||||
func GetFiles(searchQuery string) ([]models.Document, error) {
|
||||
// Declare a slice to hold the results of the query
|
||||
var documents []models.Document
|
||||
|
||||
// Perform the query to find documents where:
|
||||
// - 'deleted_at' is NULL (i.e., the document has not been logically deleted)
|
||||
// - The file name matches the search query using a case-insensitive pattern match ('ILIKE')
|
||||
if err := config.DB.Where("deleted_at IS NULL AND name ILIKE ?", searchQuery).Find(&documents).Error; err != nil {
|
||||
// If there is an error during the query execution, return an empty slice and the error message
|
||||
return documents, fmt.Errorf("error retrieving documents: %v", err)
|
||||
}
|
||||
|
||||
// Return the list of documents and nil error if the query was successful
|
||||
return documents, nil
|
||||
}
|
||||
|
||||
// GetDocument retrieves a document from the database based on its `idFile` field.
|
||||
// It searches for a document with the given `idFile` and returns the document if found, or an error if not.
|
||||
// It searches for a document with the given `idFile` and returns the document if found,
|
||||
// or an error if not.
|
||||
//
|
||||
// Parameters:
|
||||
// - idFile (uuid.UUID): The unique identifier of the document to retrieve.
|
||||
//
|
||||
// Returns:
|
||||
// - *models.Document: A pointer to the document if found.
|
||||
// - error: An error is returned if the document is not found or there is a database issue.
|
||||
func GetDocument(idFile uuid.UUID) (*models.Document, error) {
|
||||
var document models.Document
|
||||
|
||||
// Perform the query to find the document by its unique `idFile` field
|
||||
if err := config.DB.Where("id_file = ?", idFile).First(&document).Error; err != nil {
|
||||
if err := config.DB.Where("deleted_at IS NULL AND id_file = ?", idFile).First(&document).Error; err != nil {
|
||||
// If no record is found, return a descriptive error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fmt.Errorf("document with idFile %v not found", idFile)
|
||||
@@ -28,8 +63,40 @@ func GetDocument(idFile uuid.UUID) (*models.Document, error) {
|
||||
return &document, nil
|
||||
}
|
||||
|
||||
// GetDocumentByFingerprint retrieves a document from the database based on its unique fingerprint.
|
||||
// It returns the document if found, or an error if not found or if any database-related issues occur.
|
||||
//
|
||||
// Parameters:
|
||||
// - fingerprint (string): The unique fingerprint of the document to retrieve.
|
||||
//
|
||||
// Returns:
|
||||
// - *models.Document: A pointer to the `Document` struct if the document is found.
|
||||
// - error: An error if the document is not found or if there is a failure during the query.
|
||||
func GetDocumentByFingerprint(fingerprint string) (*models.Document, error) {
|
||||
var document models.Document
|
||||
|
||||
// Perform the query to find the document by its unique fingerprint
|
||||
if err := config.DB.Where("fingerprint = ?", fingerprint).First(&document).Error; err != nil {
|
||||
// If no record is found, return a descriptive error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fmt.Errorf("document with fingerprint %v not found", fingerprint)
|
||||
}
|
||||
// If there is another error during retrieval, return the error
|
||||
return nil, fmt.Errorf("error while retrieving document: %v", err)
|
||||
}
|
||||
|
||||
// Return the document if found
|
||||
return &document, nil
|
||||
}
|
||||
|
||||
// AddDocument adds a new document to the database.
|
||||
// The function receives a pointer to a `Document` struct and attempts to insert it into the database.
|
||||
//
|
||||
// Parameters:
|
||||
// - document (*models.Document): A pointer to the document to add to the database.
|
||||
//
|
||||
// Returns:
|
||||
// - error: Returns an error if there is an issue during the insertion, or nil if successful.
|
||||
func AddDocument(document *models.Document) error {
|
||||
// Create a new record for the document in the database
|
||||
if err := config.DB.Create(document).Error; err != nil {
|
||||
@@ -39,3 +106,37 @@ func AddDocument(document *models.Document) error {
|
||||
// If the operation is successful, return nil (no error)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteDocument deletes a document from the database by its associated idFile.
|
||||
// This function searches for a document by `idFile`, and if found, deletes it from the database.
|
||||
//
|
||||
// Parameters:
|
||||
// - idFile (uuid.UUID): The unique identifier of the document to delete.
|
||||
//
|
||||
// Returns:
|
||||
// - error: Returns an error if the document is not found or if there is a failure during deletion.
|
||||
func DeleteDocument(idFile uuid.UUID) error {
|
||||
// Declare a variable to hold the document from the database.
|
||||
var document models.Document
|
||||
|
||||
// Retrieve the document using the provided idFile.
|
||||
// The 'Where' clause filters by the 'id_file' field.
|
||||
// 'First' retrieves the first matching record (if any).
|
||||
if err := config.DB.Where("id_file = ?", idFile).First(&document).Error; err != nil {
|
||||
// If the record is not found, return a custom error.
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fmt.Errorf("document with idFile %v not found", idFile)
|
||||
}
|
||||
// For any other error (e.g., database connection issues), return a generic error.
|
||||
return fmt.Errorf("error while fetching document: %v", err)
|
||||
}
|
||||
|
||||
// If document is found, proceed to delete it.
|
||||
if err := config.DB.Delete(&document).Error; err != nil {
|
||||
// Return an error if the deletion failed.
|
||||
return fmt.Errorf("error while deleting document: %v", err)
|
||||
}
|
||||
|
||||
// If no error occurred, return nil (indicating success).
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,15 @@ import (
|
||||
)
|
||||
|
||||
// GetFileFromMinIO retrieves a file from the specified MinIO bucket.
|
||||
// It returns the file object if found, or an error if there is an issue with fetching the file.
|
||||
//
|
||||
// Parameters:
|
||||
// - bucketName (string): The name of the MinIO bucket to fetch the file from.
|
||||
// - objectName (string): The name of the object (file) to retrieve from the bucket.
|
||||
//
|
||||
// Returns:
|
||||
// - *minio.Object: The file object retrieved from MinIO.
|
||||
// - error: An error is returned if there is an issue fetching the object from MinIO.
|
||||
func GetFileFromMinIO(bucketName, objectName string) (*minio.Object, error) {
|
||||
// Fetch the object from MinIO using the provided bucket name and object name
|
||||
object, err := config.MinIO.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
|
||||
@@ -21,6 +30,16 @@ func GetFileFromMinIO(bucketName, objectName string) (*minio.Object, error) {
|
||||
}
|
||||
|
||||
// UploadFileToMinIO uploads a file to MinIO under the specified bucket and object name.
|
||||
// If the bucket does not exist, it is created first.
|
||||
//
|
||||
// Parameters:
|
||||
// - ctx (context.Context): The context for the operation (to control request lifetime).
|
||||
// - bucketName (string): The name of the MinIO bucket to upload the file to.
|
||||
// - objectName (string): The name of the object (file) in MinIO.
|
||||
// - filePath (string): The local file path of the file to upload.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error is returned if there is any issue during file upload.
|
||||
func UploadFileToMinIO(ctx context.Context, bucketName, objectName, filePath string) error {
|
||||
// Open the file from the given file path
|
||||
file, err := os.Open(filePath)
|
||||
@@ -47,6 +66,14 @@ func UploadFileToMinIO(ctx context.Context, bucketName, objectName, filePath str
|
||||
}
|
||||
|
||||
// createBucketIfNotExists checks if the specified bucket exists and creates it if it doesn't.
|
||||
// It is called by `UploadFileToMinIO` to ensure the bucket is available before uploading.
|
||||
//
|
||||
// Parameters:
|
||||
// - ctx (context.Context): The context for the operation (to control request lifetime).
|
||||
// - bucketName (string): The name of the bucket to check/create.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error is returned if the bucket checking or creation process fails.
|
||||
func createBucketIfNotExists(ctx context.Context, bucketName string) error {
|
||||
// Check if the bucket already exists
|
||||
exists, err := config.MinIO.BucketExists(ctx, bucketName)
|
||||
@@ -71,6 +98,14 @@ func createBucketIfNotExists(ctx context.Context, bucketName string) error {
|
||||
}
|
||||
|
||||
// DeleteFileFromMinIO removes a file from the specified MinIO bucket.
|
||||
//
|
||||
// Parameters:
|
||||
// - ctx (context.Context): The context for the operation (to control request lifetime).
|
||||
// - bucketName (string): The name of the MinIO bucket where the file is stored.
|
||||
// - objectName (string): The name of the object (file) to delete.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error is returned if there is an issue deleting the file from MinIO.
|
||||
func DeleteFileFromMinIO(ctx context.Context, bucketName, objectName string) error {
|
||||
// Remove the object from the MinIO bucket
|
||||
err := config.MinIO.RemoveObject(ctx, bucketName, objectName, minio.RemoveObjectOptions{})
|
||||
|
||||
Reference in New Issue
Block a user