Initial commit

This commit is contained in:
Fabio Scotto di Santolo
2025-05-31 22:04:55 +02:00
parent 15acea9120
commit 1a5d437311
17 changed files with 392 additions and 0 deletions

120
.gitignore vendored Normal file
View File

@@ -0,0 +1,120 @@
### GoLand+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
*.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
### Linux template
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### Windows template
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk

10
.idea/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Environment-dependent path to Maven home directory
/mavenHomeManager.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

7
.idea/codeStyles/Project.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<ScalaCodeStyleSettings>
<option name="MULTILINE_STRING_CLOSING_QUOTES_ON_NEW_LINE" value="true" />
</ScalaCodeStyleSettings>
</code_scheme>
</component>

5
.idea/codeStyles/codeStyleConfig.xml generated Normal file
View File

@@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
</state>
</component>

15
.idea/git_toolbox_prj.xml generated Normal file
View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitToolBoxProjectSettings">
<option name="commitMessageIssueKeyValidationOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
<option name="commitMessageValidationEnabledOverride">
<BoolValueOverride>
<option name="enabled" value="true" />
</BoolValueOverride>
</option>
</component>
</project>

6
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/fileserver.iml" filepath="$PROJECT_DIR$/fileserver.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

57
cmd/fileserver/main.go Normal file
View File

@@ -0,0 +1,57 @@
package main
import (
"fileserver/config"
"fileserver/internal/api"
"fmt"
"log"
"net/http"
"os"
"reflect"
"runtime"
)
func main() {
defer func() {
if r := recover(); r != nil {
log.Fatalf("Catch fatal error: %v\n", r)
}
}()
profile := defaultValue(os.Getenv("APP_PROFILE"), "prod")
if err := config.Initialize(profile); err != nil {
log.Fatalf("Error to read %s configuration: %v\n", profile, err)
}
log.Printf("Application starting with profile: %s", profile)
server := config.App.Server
mux := http.NewServeMux()
// Register all routes in the new ServeMux
log.Printf("Register all routes\n")
for url, handler := range api.Routes {
log.Printf("Register route %s for %v", url, getFunctionName(handler))
mux.HandleFunc(url, handler)
}
url := fmt.Sprintf("%s:%d", server.Host, server.Port)
log.Printf("Start server on %s\n", url)
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
}

View File

@@ -0,0 +1,6 @@
{
"server": {
"host": "localhost",
"port": 8080
}
}

6
config/application.json Normal file
View File

@@ -0,0 +1,6 @@
{
"server": {
"host": "localhost",
"port": 8089
}
}

49
config/config.go Normal file
View File

@@ -0,0 +1,49 @@
package config
import (
"encoding/json"
"fmt"
"log"
"os"
)
type Application struct {
Server Server `json:"server"`
}
type Server struct {
Host string `json:"host"`
Port int `json:"port"`
}
var App Application
const configDir = "config"
func Initialize(profile string) error {
filename := checkProfileAndGetFilePath(profile)
content, err := os.ReadFile(filename)
if err != nil {
return fmt.Errorf("error reading file: %v", err)
}
if err = json.Unmarshal(content, &App); err != nil {
return fmt.Errorf("error unmarshaling JSON: %v", err)
}
return nil
}
func checkProfileAndGetFilePath(profile string) string {
var filename string
switch {
case profile == "dev":
filename = fmt.Sprintf("%s/application-dev.json", configDir)
case profile == "test":
filename = fmt.Sprintf("%s/application-test.json", configDir)
case profile == "prod":
filename = fmt.Sprintf("%s/application.json", configDir)
default:
log.Fatalf("Profile %s is not valid value", profile)
}
return filename
}

3
go.mod Normal file
View File

@@ -0,0 +1,3 @@
module fileserver
go 1.24

View File

@@ -0,0 +1,76 @@
package api
import (
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
"strings"
"time"
)
func LoadFile(w http.ResponseWriter, r *http.Request) {
// Make sure the request is a POST and is of type multipart/form-data
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Limit the maximum request size (e.g., 10 MB)
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
file, header, err := r.FormFile("file")
if err != nil {
http.Error(w, "Error retrieving the file: "+err.Error(), http.StatusInternalServerError)
return
}
defer func(file multipart.File) {
err := file.Close()
if err != nil {
http.Error(w, "Error closing the input file: "+err.Error(), http.StatusInternalServerError)
}
}(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)
if err != nil {
http.Error(w, "Error creating the uploads folder", http.StatusInternalServerError)
return
}
// Use a unique name for the file (timestamp)
newFileName := fmt.Sprintf("%d_%s", time.Now().Unix(), header.Filename)
out, err := os.Create("./uploads/" + newFileName)
if err != nil {
http.Error(w, "Error saving the file: "+err.Error(), http.StatusInternalServerError)
return
}
defer func(out *os.File) {
err := out.Close()
if err != nil {
http.Error(w, "Error closing the output file: "+err.Error(), http.StatusInternalServerError)
}
}(out)
// Copy the file contents from the request to the saved 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)
}

8
internal/api/routes.go Normal file
View File

@@ -0,0 +1,8 @@
package api
import "net/http"
var Routes = map[string]func(w http.ResponseWriter, r *http.Request){
"/": Hello,
"/file": LoadFile,
}

10
internal/api/welcome.go Normal file
View File

@@ -0,0 +1,10 @@
package api
import "net/http"
func Hello(w http.ResponseWriter, r *http.Request) {
if _, err := w.Write([]byte("Welcome to my homepage")); err != nil {
return
}
w.WriteHeader(http.StatusOK)
}

0
pkg/.gitkeep Normal file
View File