Added db configurations and Dockerfile
This commit is contained in:
99
.dockerignore
Normal file
99
.dockerignore
Normal file
@@ -0,0 +1,99 @@
|
||||
### JetBrains+all template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
# .idea/**/workspace.xml
|
||||
# .idea/**/tasks.xml
|
||||
# .idea/**/usage.statistics.xml
|
||||
# .idea/**/dictionaries
|
||||
# .idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
# .idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
# .idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
# .idea/**/dataSources/
|
||||
# .idea/**/dataSources.ids
|
||||
# .idea/**/dataSources.local.xml
|
||||
# .idea/**/sqlDataSources.xml
|
||||
# .idea/**/dynamic.xml
|
||||
# .idea/**/uiDesigner.xml
|
||||
# .idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
# .idea/**/gradle.xml
|
||||
# .idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
.idea/
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
# .idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
# .idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
# .idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
# .idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
# .idea/caches/build_file_checksums.ser
|
||||
|
||||
### Git template
|
||||
# Created by git for backups. To disable backups in Git:
|
||||
# $ git config --global mergetool.keepBackup false
|
||||
*.orig
|
||||
|
||||
# Created by git when using merge tools for conflicts
|
||||
*.BACKUP.*
|
||||
*.BASE.*
|
||||
*.LOCAL.*
|
||||
*.REMOTE.*
|
||||
*_BACKUP_*.txt
|
||||
*_BASE_*.txt
|
||||
*_LOCAL_*.txt
|
||||
*_REMOTE_*.txt
|
||||
.git/
|
||||
.gitignore
|
||||
|
||||
## Docker Compose
|
||||
docker-compose.yaml
|
||||
26
Dockerfile
Normal file
26
Dockerfile
Normal file
@@ -0,0 +1,26 @@
|
||||
FROM golang:1.24.1-alpine3.21 AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
|
||||
# Specifica il path del main package
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -o api ./cmd/fileserver
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/api .
|
||||
COPY --from=builder /app/config/*.json ./config/
|
||||
|
||||
RUN ls -laR .
|
||||
|
||||
RUN apk --no-cache add ca-certificates
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
CMD ["./api"]
|
||||
@@ -3,55 +3,56 @@ package main
|
||||
import (
|
||||
"fileserver/config"
|
||||
"fileserver/internal/api"
|
||||
"fileserver/internal/utils"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Defer a function to catch any runtime panics and log them.
|
||||
// This helps in recovering from unexpected fatal errors.
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Fatalf("Catch fatal error: %v\n", r)
|
||||
log.Fatalf("Catch fatal error: %v\n", r) // Log the fatal error if panic occurs
|
||||
}
|
||||
}()
|
||||
profile := defaultValue(os.Getenv("APP_PROFILE"), "prod")
|
||||
|
||||
// Get the application profile from environment variables or default to "prod" if not set.
|
||||
profile := utils.DefaultValue(os.Getenv("APP_PROFILE"), "prod")
|
||||
|
||||
// Initialize the configuration for the application based on the profile.
|
||||
if err := config.Initialize(profile); err != nil {
|
||||
// If an error occurs during initialization, log the error and terminate the application.
|
||||
log.Fatalf("Error to read %s configuration: %v\n", profile, err)
|
||||
}
|
||||
|
||||
// Log the profile that is being used to start the application.
|
||||
log.Printf("Application starting with profile: %s", profile)
|
||||
// Register all routes in the new ServeMux
|
||||
|
||||
// Create a new HTTP request multiplexer (ServeMux) to register routes.
|
||||
mux := http.NewServeMux()
|
||||
log.Printf("Register all routes\n")
|
||||
|
||||
// Iterate through the routes defined in the API package and register them.
|
||||
for url, handler := range api.Routes {
|
||||
log.Printf("Register route %s for %v", url, getFunctionName(handler))
|
||||
// For each route, log the URL and corresponding handler function name.
|
||||
log.Printf("Register route %s for %v", url, utils.GetFunctionName(handler))
|
||||
// Register the route and associate it with the handler function.
|
||||
mux.HandleFunc(url, handler)
|
||||
}
|
||||
|
||||
// Get the server configuration from the app's config settings.
|
||||
server := config.App.Server
|
||||
// Format the server's host and port into a string for the URL.
|
||||
url := fmt.Sprintf("%s:%d", server.Host, server.Port)
|
||||
// Log the server's URL where it will be listening.
|
||||
log.Printf("Start server on %s\n", url)
|
||||
|
||||
// Start the HTTP server using the specified host and port, and pass in the mux for routing.
|
||||
// If an error occurs while starting the server, log it and terminate the program.
|
||||
if err := http.ListenAndServe(url, mux); err != nil {
|
||||
log.Fatalf("%v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Function to get the name of a function from its reference
|
||||
func getFunctionName(fn any) string {
|
||||
// Get the pointer to the function using reflection
|
||||
pc := reflect.ValueOf(fn).Pointer()
|
||||
|
||||
// Use the pointer to get the function object
|
||||
funcObj := runtime.FuncForPC(pc)
|
||||
|
||||
// Return the name of the function
|
||||
return funcObj.Name()
|
||||
}
|
||||
|
||||
func defaultValue(value string, other string) string {
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
return other
|
||||
}
|
||||
|
||||
@@ -2,5 +2,18 @@
|
||||
"server": {
|
||||
"host": "localhost",
|
||||
"port": 8081
|
||||
},
|
||||
"database": {
|
||||
"driver": "postgres",
|
||||
"host": "localhost",
|
||||
"port": 5432,
|
||||
"name": "fileserver",
|
||||
"username": "postgres",
|
||||
"password": "postgres"
|
||||
},
|
||||
"minio": {
|
||||
"url": "localhost:9000",
|
||||
"username": "minioadmin",
|
||||
"password": "minioadmin"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,5 +2,17 @@
|
||||
"server": {
|
||||
"host": "localhost",
|
||||
"port": 8080
|
||||
},
|
||||
"database": {
|
||||
"driver": "postgres",
|
||||
"url": "pgfileserver",
|
||||
"name": "fileserver",
|
||||
"username": "postgres",
|
||||
"password": "postgres"
|
||||
},
|
||||
"minio": {
|
||||
"url": "miniofs",
|
||||
"username": "minioadmin",
|
||||
"password": "minioadmin"
|
||||
}
|
||||
}
|
||||
|
||||
167
config/config.go
167
config/config.go
@@ -2,40 +2,107 @@ package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fileserver/internal/utils"
|
||||
"fmt"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"os"
|
||||
)
|
||||
|
||||
// Application represents the top-level structure of the application's configuration.
|
||||
type Application struct {
|
||||
Server Server `json:"server"`
|
||||
Server *Server `json:"server"` // Server configuration
|
||||
Database *Database `json:"database"` // Database configuration
|
||||
Minio *Minio `json:"minio"` // MinIO configuration
|
||||
}
|
||||
|
||||
// Server holds the configuration related to the web server (e.g., host, port).
|
||||
type Server struct {
|
||||
Host string `json:"host"`
|
||||
Port int `json:"port"`
|
||||
Host string `json:"host"` // Hostname or IP address for the server
|
||||
Port int `json:"port"` // Port number on which the server will run
|
||||
}
|
||||
|
||||
var App Application
|
||||
// Database holds the configuration for connecting to a database (e.g., Postgres or SQLite).
|
||||
type Database struct {
|
||||
Url string `json:"url"` // Database URL (used in case of SQLite)
|
||||
Driver string `json:"driver"` // Database driver (e.g., "postgres" or "sqlite")
|
||||
Host string `json:"host"` // Hostname of the database server (used in case of PostgreSQL)
|
||||
Port int `json:"port"` // Port number for the database connection
|
||||
Name string `json:"name"` // Database name
|
||||
Username string `json:"username"` // Database username
|
||||
Password string `json:"password"` // Database password
|
||||
SSLMode bool `json:"ssl-mode"` // Whether SSL is enabled for the connection
|
||||
Timezone string `json:"timezone"` // Timezone for the database connection
|
||||
}
|
||||
|
||||
const configDir = "config"
|
||||
// Minio holds the configuration for connecting to a MinIO server.
|
||||
type Minio struct {
|
||||
Url string `json:"url"` // MinIO server URL
|
||||
Username string `json:"username"` // MinIO username
|
||||
Password string `json:"password"` // MinIO password
|
||||
Token string `json:"token"` // Optional token for MinIO authentication
|
||||
Secure bool `json:"secure"` // Whether the connection is secure (HTTPS)
|
||||
Region string `json:"region"` // MinIO server region
|
||||
BucketLookup int `json:"bucketLookup"` // Bucket lookup strategy
|
||||
}
|
||||
|
||||
// Global variables for the application configuration and clients.
|
||||
var (
|
||||
App Application // Application-level configuration
|
||||
DB *gorm.DB // Database client (GORM)
|
||||
MinIO *minio.Client // MinIO client
|
||||
)
|
||||
|
||||
const (
|
||||
configDir = "config" // Directory where the configuration files are stored
|
||||
)
|
||||
|
||||
// Initialize reads the configuration file based on the profile (dev, test, prod),
|
||||
// and initializes the MinIO and database clients based on the configuration.
|
||||
func Initialize(profile string) error {
|
||||
// Get the file path based on the profile
|
||||
filename, err := checkProfileAndGetFilePath(profile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("errore checking profile: %v", err)
|
||||
return fmt.Errorf("error checking profile: %v", err)
|
||||
}
|
||||
|
||||
// Read the configuration file
|
||||
content, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error reading file: %v", err)
|
||||
}
|
||||
|
||||
// Unmarshal the JSON content into the Application structure
|
||||
if err = json.Unmarshal(content, &App); err != nil {
|
||||
return fmt.Errorf("error unmarshaling JSON: %v", err)
|
||||
}
|
||||
|
||||
// Initialize MinIO if MinIO configuration is provided
|
||||
if App.Minio != nil {
|
||||
if err := initializeMinIO(App.Minio); err != nil {
|
||||
return fmt.Errorf("error initializing MinIO: %v", err)
|
||||
}
|
||||
fmt.Println("MinIO initialized")
|
||||
}
|
||||
|
||||
// Initialize database if database configuration is provided
|
||||
if App.Database != nil {
|
||||
if err := initializeDatabase(App.Database); err != nil {
|
||||
return fmt.Errorf("error initializing database: %v", err)
|
||||
}
|
||||
fmt.Println("Database initialized")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkProfileAndGetFilePath returns the correct configuration file path based on the profile (dev, test, prod).
|
||||
func checkProfileAndGetFilePath(profile string) (string, error) {
|
||||
var filename string
|
||||
// Match the profile to its respective configuration file
|
||||
switch {
|
||||
case profile == "dev":
|
||||
filename = fmt.Sprintf("%s/application-dev.json", configDir)
|
||||
@@ -44,7 +111,93 @@ func checkProfileAndGetFilePath(profile string) (string, error) {
|
||||
case profile == "prod":
|
||||
filename = fmt.Sprintf("%s/application.json", configDir)
|
||||
default:
|
||||
return "", fmt.Errorf("profile %s is not valid value", profile)
|
||||
return "", fmt.Errorf("profile %s is not valid", profile)
|
||||
}
|
||||
return filename, nil
|
||||
}
|
||||
|
||||
// initializeMinIO initializes the MinIO client using the provided configuration.
|
||||
func initializeMinIO(minioConfig *Minio) error {
|
||||
// Create a MinIO client with the given credentials and options
|
||||
client, err := minio.New(minioConfig.Url, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(minioConfig.Username, minioConfig.Password, minioConfig.Token),
|
||||
Secure: minioConfig.Secure,
|
||||
Region: minioConfig.Region,
|
||||
BucketLookup: getBucketLookup(minioConfig.BucketLookup),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot connect to MinIO %s: %v", minioConfig.Url, err)
|
||||
}
|
||||
MinIO = client
|
||||
return nil
|
||||
}
|
||||
|
||||
// getBucketLookup maps the integer value to the appropriate MinIO bucket lookup type.
|
||||
func getBucketLookup(value int) minio.BucketLookupType {
|
||||
switch value {
|
||||
case 0:
|
||||
return minio.BucketLookupAuto
|
||||
case 1:
|
||||
return minio.BucketLookupDNS
|
||||
case 2:
|
||||
return minio.BucketLookupPath
|
||||
default:
|
||||
return minio.BucketLookupAuto
|
||||
}
|
||||
}
|
||||
|
||||
// initializeDatabase initializes the database client based on the provided configuration.
|
||||
func initializeDatabase(dbConfig *Database) error {
|
||||
// Generate the database connection string based on the driver
|
||||
switch dbConfig.Driver {
|
||||
case "postgres":
|
||||
var url string
|
||||
if dbConfig.Url != "" {
|
||||
url = fmt.Sprintf(
|
||||
"postgres://%s:%s@%s/%s?sslmode=%s&TimeZone=%s",
|
||||
dbConfig.Username,
|
||||
dbConfig.Password,
|
||||
dbConfig.Url,
|
||||
utils.DefaultValue(dbConfig.Name, "postgres"),
|
||||
getSSLModeValue(dbConfig.SSLMode),
|
||||
utils.DefaultValue(dbConfig.Timezone, "UTC"),
|
||||
)
|
||||
} else {
|
||||
url = fmt.Sprintf(
|
||||
"host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=%s",
|
||||
dbConfig.Host,
|
||||
dbConfig.Username,
|
||||
dbConfig.Password,
|
||||
utils.DefaultValue(dbConfig.Name, "postgres"),
|
||||
dbConfig.Port,
|
||||
getSSLModeValue(dbConfig.SSLMode),
|
||||
utils.DefaultValue(dbConfig.Timezone, "UTC"),
|
||||
)
|
||||
}
|
||||
|
||||
// Open PostgreSQL connection with GORM
|
||||
db, err := gorm.Open(postgres.Open(url), &gorm.Config{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot connect to database %s@%s:%d", dbConfig.Username, dbConfig.Host, dbConfig.Port)
|
||||
}
|
||||
DB = db
|
||||
case "sqlite":
|
||||
// Open SQLite connection with GORM
|
||||
db, err := gorm.Open(sqlite.Open(dbConfig.Url), &gorm.Config{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot connect to database %s", dbConfig.Url)
|
||||
}
|
||||
DB = db
|
||||
default:
|
||||
return fmt.Errorf("database type is not supported")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getSSLModeValue returns "enable" or "disable" based on the boolean value for SSL mode.
|
||||
func getSSLModeValue(mode bool) string {
|
||||
if !mode {
|
||||
return "disable"
|
||||
}
|
||||
return "enable"
|
||||
}
|
||||
|
||||
50
docker-compose.yaml
Normal file
50
docker-compose.yaml
Normal file
@@ -0,0 +1,50 @@
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:15
|
||||
container_name: pgfileserver
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: fileserver
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- pg_data:/var/lib/postgresql/data
|
||||
networks:
|
||||
- backend_net
|
||||
|
||||
minio:
|
||||
image: minio/minio:latest
|
||||
container_name: miniofs
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MINIO_ROOT_USER: minioadmin
|
||||
MINIO_ROOT_PASSWORD: minioadmin
|
||||
command: server /data --console-address ":9001"
|
||||
ports:
|
||||
- "9000:9000" # API
|
||||
- "9001:9001" # Console
|
||||
volumes:
|
||||
- minio_data:/data
|
||||
networks:
|
||||
- backend_net
|
||||
|
||||
fileserver:
|
||||
image: fileserver:latest
|
||||
container_name: fileserver
|
||||
restart: always
|
||||
ports:
|
||||
- "8080:8081"
|
||||
networks:
|
||||
- backend_net
|
||||
|
||||
volumes:
|
||||
pg_data:
|
||||
minio_data:
|
||||
|
||||
networks:
|
||||
backend_net:
|
||||
driver: bridge
|
||||
33
go.mod
33
go.mod
@@ -1,3 +1,36 @@
|
||||
module fileserver
|
||||
|
||||
go 1.24
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/minio/minio-go/v7 v7.0.92
|
||||
gorm.io/driver/postgres v1.6.0
|
||||
gorm.io/driver/sqlite v1.5.7
|
||||
gorm.io/gorm v1.30.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/go-ini/ini v1.67.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.5 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.28 // indirect
|
||||
github.com/minio/crc64nvme v1.0.2 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
|
||||
github.com/rs/xid v1.6.0 // indirect
|
||||
github.com/tinylib/msgp v1.3.0 // indirect
|
||||
golang.org/x/crypto v0.38.0 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
)
|
||||
|
||||
69
go.sum
Normal file
69
go.sum
Normal file
@@ -0,0 +1,69 @@
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
|
||||
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
||||
github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/minio/crc64nvme v1.0.2 h1:6uO1UxGAD+kwqWWp7mBFsi5gAse66C4NXO8cmcVculg=
|
||||
github.com/minio/crc64nvme v1.0.2/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.92 h1:jpBFWyRS3p8P/9tsRc+NuvqoFi7qAmTCFPoRFmobbVw=
|
||||
github.com/minio/minio-go/v7 v7.0.92/go.mod h1:vTIc8DNcnAZIhyFsk8EB90AbPjj3j68aWIEQCiPj7d0=
|
||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
|
||||
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tinylib/msgp v1.3.0 h1:ULuf7GPooDaIlbyvgAxBV/FI7ynli6LZ1/nVUNu+0ww=
|
||||
github.com/tinylib/msgp v1.3.0/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||
gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
|
||||
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
|
||||
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
@@ -1,30 +1,104 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fileserver/internal/service"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Constants for default bucket name and folder path for uploaded files
|
||||
const (
|
||||
defaultBucketName = "documents" // Default bucket name in MinIO
|
||||
localFolderTemplate = "%s/fileserver/uploads/" // Template for creating local upload directories
|
||||
)
|
||||
|
||||
// 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
|
||||
if r.Method != http.MethodGet {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the object name from the query parameters
|
||||
objectName := r.URL.Query().Get("file")
|
||||
// Fetch the file object from MinIO storage
|
||||
object, err := service.GetFileFromMinIO(defaultBucketName, objectName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer object.Close() // Ensure that the file object is closed after use
|
||||
|
||||
// Create the upload directory if it doesn't exist
|
||||
uploadDir := fmt.Sprintf(localFolderTemplate, os.TempDir())
|
||||
err = os.MkdirAll(uploadDir, os.ModePerm)
|
||||
if err != nil {
|
||||
http.Error(w, "Error creating the uploads folder", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Create a new file locally with a unique name (using a timestamp)
|
||||
newFileName := fmt.Sprintf("%d_%s", time.Now().Unix(), objectName)
|
||||
file, err := os.Create(uploadDir + newFileName)
|
||||
if err != nil {
|
||||
http.Error(w, "Error saving the file: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer func(file *os.File) {
|
||||
err := file.Close()
|
||||
if err != nil {
|
||||
http.Error(w, "Error closing file: "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}(file)
|
||||
|
||||
// Copy the file content from MinIO to the local file
|
||||
_, err = io.Copy(file, object)
|
||||
if err != nil {
|
||||
fmt.Println("Error saving object to file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the file's information (size, name, etc.)
|
||||
fileInfo, err := file.Stat()
|
||||
if err != nil {
|
||||
http.Error(w, "Could not get file information", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Set headers for file download (name, content type, and length)
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; objectName=%s", fileInfo.Name()))
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", fileInfo.Size()))
|
||||
|
||||
// Log the successful file retrieval
|
||||
fmt.Printf("Sending file: %s (Size: %d bytes)\n", fileInfo.Name(), fileInfo.Size())
|
||||
|
||||
// Serve the file content as a download
|
||||
http.ServeContent(w, r, fileInfo.Name(), fileInfo.ModTime(), file)
|
||||
}
|
||||
|
||||
// LoadFile handles file uploads from a client and stores them locally and on MinIO.
|
||||
func LoadFile(w http.ResponseWriter, r *http.Request) {
|
||||
// Make sure the request is a POST and is of type multipart/form-data
|
||||
// Ensure that the request method is POST and that it is a multipart form
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
// Limit the maximum request size (e.g., 10 MB)
|
||||
// Parse the multipart form data with a maximum file size of 10MB
|
||||
err := r.ParseMultipartForm(10 << 20) // 10 MB
|
||||
if err != nil {
|
||||
http.Error(w, "Error parsing the request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Retrieve the file from the 'file' field of the form
|
||||
// Retrieve the uploaded file from the form
|
||||
file, header, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
http.Error(w, "Error retrieving the file: "+err.Error(), http.StatusInternalServerError)
|
||||
@@ -37,22 +111,17 @@ func LoadFile(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}(file)
|
||||
|
||||
// Check the file extension (example)
|
||||
if !strings.HasSuffix(header.Filename, ".txt") {
|
||||
http.Error(w, "Unsupported file type", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Create uploads folder if it doesn't exist
|
||||
err = os.MkdirAll("./uploads", os.ModePerm)
|
||||
// Create the upload directory if it doesn't exist
|
||||
uploadDir := fmt.Sprintf(localFolderTemplate, os.TempDir())
|
||||
err = os.MkdirAll(uploadDir, os.ModePerm)
|
||||
if err != nil {
|
||||
http.Error(w, "Error creating the uploads folder", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Use a unique name for the file (timestamp)
|
||||
// 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("./uploads/" + newFileName)
|
||||
out, err := os.Create(uploadDir + newFileName)
|
||||
if err != nil {
|
||||
http.Error(w, "Error saving the file: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -62,15 +131,51 @@ func LoadFile(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
http.Error(w, "Error closing the output file: "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
if err := cleanup(out); err != nil {
|
||||
http.Error(w, "Error remove the output file: "+err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}(out)
|
||||
|
||||
// Copy the file contents from the request to the saved file
|
||||
// Copy the file content from the request to the local file
|
||||
_, err = io.Copy(out, file)
|
||||
if err != nil {
|
||||
http.Error(w, "Error copying the file", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Respond with a success message
|
||||
fmt.Fprintf(w, "File %s uploaded successfully!", newFileName)
|
||||
// Upload the file to MinIO with a unique ID (UUID)
|
||||
idFile := uuid.New().String()
|
||||
err = service.UploadFileToMinIO(context.Background(), defaultBucketName, idFile, uploadDir+newFileName)
|
||||
if err != nil {
|
||||
http.Error(w, "", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Respond to the client with a success message
|
||||
_, err = fmt.Fprintf(w, "File %s uploaded successfully!\n", newFileName)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup removes a file from the local file system after use.
|
||||
func cleanup(file *os.File) error {
|
||||
if _, err := os.Stat(file.Name()); err == nil {
|
||||
err := os.Remove(file.Name())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error removing file: %v", err)
|
||||
} else {
|
||||
fmt.Println("File removed successfully:", file.Name())
|
||||
}
|
||||
} else if os.IsNotExist(err) {
|
||||
return fmt.Errorf("File does not exist: %v", file.Name())
|
||||
} else {
|
||||
fmt.Println("Error checking file:", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteFile handles the file deletion logic (currently empty).
|
||||
func DeleteFile(w http.ResponseWriter, r *http.Request) {
|
||||
// This function is a placeholder for file deletion logic.
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package api
|
||||
import "net/http"
|
||||
|
||||
var Routes = map[string]func(w http.ResponseWriter, r *http.Request){
|
||||
"/": Hello,
|
||||
"/file": LoadFile,
|
||||
"GET /": Hello,
|
||||
"GET /file": GetFile,
|
||||
"POST /file": LoadFile,
|
||||
"DELETE /file": DeleteFile,
|
||||
}
|
||||
|
||||
24
internal/models/document.go
Normal file
24
internal/models/document.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Document represents the structure of the documents table in the database.
|
||||
type Document struct {
|
||||
ID uint `gorm:"primaryKey"` // Primary key for the document
|
||||
Name string `gorm:"column:name"` // Name of the document
|
||||
IdFile uuid.UUID `gorm:"type:uuid;column:id_file;unique"` // Unique identifier for the document's file
|
||||
Fingerprint string `gorm:"column:fingerprint;unique"` // Unique fingerprint (hash) for the document
|
||||
CreatedAt time.Time `gorm:"column:created_at"` // Timestamp of when the document was created
|
||||
UpdatedAt time.Time `gorm:"column:updated_at"` // Timestamp of when the document was last updated
|
||||
DeletedAt gorm.DeletedAt `gorm:"index;column:deleted_at"` // Timestamp for soft deletion (if applicable)
|
||||
}
|
||||
|
||||
// TableName overrides the default table name used by GORM.
|
||||
func (Document) TableName() string {
|
||||
// Returns the name of the table where documents are stored
|
||||
return "documents"
|
||||
}
|
||||
41
internal/service/document_service.go
Normal file
41
internal/service/document_service.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fileserver/config"
|
||||
"fileserver/internal/models"
|
||||
"fmt"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 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.
|
||||
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 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)
|
||||
}
|
||||
// If there is another error during retrieval, return the error
|
||||
return nil, fmt.Errorf("error while retrieving document: %v", err)
|
||||
}
|
||||
|
||||
// Return the document found in the database
|
||||
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.
|
||||
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 {
|
||||
// If an error occurs during the insert, return the error
|
||||
return fmt.Errorf("error while adding document: %v", err)
|
||||
}
|
||||
// If the operation is successful, return nil (no error)
|
||||
return nil
|
||||
}
|
||||
85
internal/service/minio_service.go
Normal file
85
internal/service/minio_service.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fileserver/config"
|
||||
"fmt"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"os"
|
||||
)
|
||||
|
||||
// GetFileFromMinIO retrieves a file from the specified MinIO bucket.
|
||||
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{})
|
||||
if err != nil {
|
||||
// Return error if there is any issue in fetching the object
|
||||
return nil, fmt.Errorf("error getting object from MinIO: %v", err)
|
||||
}
|
||||
// Return the fetched object if successful
|
||||
return object, nil
|
||||
}
|
||||
|
||||
// UploadFileToMinIO uploads a file to MinIO under the specified bucket and object name.
|
||||
func UploadFileToMinIO(ctx context.Context, bucketName, objectName, filePath string) error {
|
||||
// Open the file from the given file path
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
// Return error if unable to open the file
|
||||
return fmt.Errorf("failed to open file: %v", err)
|
||||
}
|
||||
defer file.Close() // Ensure file is closed after use
|
||||
|
||||
// Check if the bucket exists, create it if not
|
||||
if err = createBucketIfNotExists(ctx, bucketName); err != nil {
|
||||
// Return error if bucket creation fails
|
||||
return fmt.Errorf("failed to create bucket: %v", err)
|
||||
}
|
||||
|
||||
// Upload the file to MinIO
|
||||
_, err = config.MinIO.PutObject(ctx, bucketName, objectName, file, -1, minio.PutObjectOptions{ContentType: "application/octet-stream"})
|
||||
if err != nil {
|
||||
// Return error if uploading the file fails
|
||||
return fmt.Errorf("failed to upload file: %v", err)
|
||||
}
|
||||
// Return nil if the file is successfully uploaded
|
||||
return nil
|
||||
}
|
||||
|
||||
// createBucketIfNotExists checks if the specified bucket exists and creates it if it doesn't.
|
||||
func createBucketIfNotExists(ctx context.Context, bucketName string) error {
|
||||
// Check if the bucket already exists
|
||||
exists, err := config.MinIO.BucketExists(ctx, bucketName)
|
||||
if err != nil {
|
||||
// Return error if checking the bucket existence fails
|
||||
return fmt.Errorf("failed to check if bucket exists: %v", err)
|
||||
}
|
||||
|
||||
// If the bucket doesn't exist, create it
|
||||
if !exists {
|
||||
fmt.Println("Bucket does not exist. Creating bucket...")
|
||||
// Create the bucket with the specified region
|
||||
err = config.MinIO.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{Region: "us-east-1"})
|
||||
if err != nil {
|
||||
// Return error if bucket creation fails
|
||||
return fmt.Errorf("failed to create bucket: %v", err)
|
||||
}
|
||||
fmt.Println("Bucket created successfully!")
|
||||
}
|
||||
// Return nil if bucket already exists or is created successfully
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteFileFromMinIO removes a file from the specified MinIO bucket.
|
||||
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{})
|
||||
if err != nil {
|
||||
// Return error if deleting the object fails
|
||||
return fmt.Errorf("error deleting object from MinIO: %v", err)
|
||||
}
|
||||
// Log success message after deletion
|
||||
fmt.Println("File deleted successfully")
|
||||
// Return nil if file is deleted successfully
|
||||
return nil
|
||||
}
|
||||
57
internal/utils/fingerprint.go
Normal file
57
internal/utils/fingerprint.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// CalculateFingerprint calculates the fingerprint (SHA-1 hash) of a file at a given path.
|
||||
//
|
||||
// This function computes a SHA-1 hash for the contents of a file. It opens the file, reads it in chunks
|
||||
// to avoid loading the entire file into memory, and then calculates the hash using the SHA-1 algorithm.
|
||||
// Finally, it returns the resulting hash as a hexadecimal string.
|
||||
//
|
||||
// Parameters:
|
||||
// - filePath (string): The path to the file whose fingerprint (hash) is to be calculated.
|
||||
//
|
||||
// Returns:
|
||||
// - string: The SHA-1 hash of the file, represented as a hexadecimal string.
|
||||
// - error: Any error encountered while opening the file, reading it, or calculating the hash.
|
||||
// If no error occurred, it returns nil.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// fingerprint, err := utils.CalculateFingerprint("/path/to/file.txt")
|
||||
// if err != nil {
|
||||
// log.Fatalf("Error calculating fingerprint: %v", err)
|
||||
// }
|
||||
// fmt.Printf("Fingerprint: %s\n", fingerprint)
|
||||
func CalculateFingerprint(filePath string) (string, error) {
|
||||
// Open the file at the specified file path.
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to open file: %v", err)
|
||||
}
|
||||
// Ensure that the file is closed after processing (using a defer statement).
|
||||
defer func(file *os.File) {
|
||||
err := file.Close()
|
||||
if err != nil {
|
||||
log.Fatalf("failed to close file: %v", err)
|
||||
}
|
||||
}(file)
|
||||
|
||||
// Create a new SHA-1 hash object.
|
||||
hash := sha1.New()
|
||||
|
||||
// Read the file and calculate the hash while reading. The entire file is not loaded into memory.
|
||||
_, err = io.Copy(hash, file)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to calculate hash: %v", err)
|
||||
}
|
||||
|
||||
// Return the final hash as a hexadecimal string.
|
||||
return fmt.Sprintf("%x", hash.Sum(nil)), nil
|
||||
}
|
||||
55
internal/utils/strings.go
Normal file
55
internal/utils/strings.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// GetFunctionName returns the name of a function from its reference.
|
||||
//
|
||||
// This function uses reflection to get the function's pointer and
|
||||
// retrieves its name using the runtime package. It can be used to
|
||||
// dynamically obtain the name of a function at runtime.
|
||||
//
|
||||
// Parameters:
|
||||
// - fn (any): A reference to the function whose name you want to retrieve.
|
||||
//
|
||||
// Returns:
|
||||
// - string: The name of the function, typically in the form of "packageName.funcName".
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// func example() {}
|
||||
// name := utils.GetFunctionName(example) // Returns "main.example"
|
||||
func GetFunctionName(fn any) string {
|
||||
// Get the pointer to the function using reflection
|
||||
pc := reflect.ValueOf(fn).Pointer()
|
||||
|
||||
// Use the pointer to get the function object
|
||||
funcObj := runtime.FuncForPC(pc)
|
||||
|
||||
// Return the name of the function
|
||||
return funcObj.Name()
|
||||
}
|
||||
|
||||
// DefaultValue checks if a given value is non-empty and returns it.
|
||||
// If the value is empty, it returns a fallback (default) value.
|
||||
//
|
||||
// Parameters:
|
||||
// - value (string): The value to check for non-emptiness.
|
||||
// - other (string): The fallback value to return if the input `value` is empty.
|
||||
//
|
||||
// Returns:
|
||||
// - string: The `value` if it is non-empty, otherwise the `other` value.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// result := utils.DefaultValue("", "default") // Returns "default"
|
||||
// result := utils.DefaultValue("custom", "default") // Returns "custom"
|
||||
func DefaultValue(value string, other string) string {
|
||||
// Return `value` if it is not empty, otherwise return `other`
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
return other
|
||||
}
|
||||
13
scripts/database/db.sql
Normal file
13
scripts/database/db.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
CREATE TABLE IF NOT EXISTS documents
|
||||
(
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
id_file UUID UNIQUE NOT NULL,
|
||||
fingerprint TEXT UNIQUE NOT NULL,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT now(),
|
||||
deleted_at TIMESTAMP WITHOUT TIME ZONE
|
||||
);
|
||||
|
||||
-- Crea un indice su deleted_at per il supporto soft delete
|
||||
CREATE INDEX IF NOT EXISTS idx_documents_deleted_at ON documents (deleted_at);
|
||||
Reference in New Issue
Block a user