diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ef40fab
--- /dev/null
+++ b/.gitignore
@@ -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
+
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..18d7b81
--- /dev/null
+++ b/.idea/.gitignore
@@ -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
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..c2621b8
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..df5f35d
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/git_toolbox_prj.xml b/.idea/git_toolbox_prj.xml
new file mode 100644
index 0000000..02b915b
--- /dev/null
+++ b/.idea/git_toolbox_prj.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..9715c22
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..d4c7d02
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/cmd/fileserver/main.go b/cmd/fileserver/main.go
new file mode 100644
index 0000000..3f062a0
--- /dev/null
+++ b/cmd/fileserver/main.go
@@ -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
+}
diff --git a/config/application-dev.json b/config/application-dev.json
new file mode 100644
index 0000000..f6787bc
--- /dev/null
+++ b/config/application-dev.json
@@ -0,0 +1,6 @@
+{
+ "server": {
+ "host": "localhost",
+ "port": 8080
+ }
+}
diff --git a/config/application.json b/config/application.json
new file mode 100644
index 0000000..26decab
--- /dev/null
+++ b/config/application.json
@@ -0,0 +1,6 @@
+{
+ "server": {
+ "host": "localhost",
+ "port": 8089
+ }
+}
diff --git a/config/config.go b/config/config.go
new file mode 100644
index 0000000..7f09972
--- /dev/null
+++ b/config/config.go
@@ -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
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..ca5fa2d
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module fileserver
+
+go 1.24
diff --git a/internal/api/file_handler.go b/internal/api/file_handler.go
new file mode 100644
index 0000000..11cb01b
--- /dev/null
+++ b/internal/api/file_handler.go
@@ -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)
+}
diff --git a/internal/api/routes.go b/internal/api/routes.go
new file mode 100644
index 0000000..8a80716
--- /dev/null
+++ b/internal/api/routes.go
@@ -0,0 +1,8 @@
+package api
+
+import "net/http"
+
+var Routes = map[string]func(w http.ResponseWriter, r *http.Request){
+ "/": Hello,
+ "/file": LoadFile,
+}
diff --git a/internal/api/welcome.go b/internal/api/welcome.go
new file mode 100644
index 0000000..fede4df
--- /dev/null
+++ b/internal/api/welcome.go
@@ -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)
+}
diff --git a/pkg/.gitkeep b/pkg/.gitkeep
new file mode 100644
index 0000000..e69de29