axyut commited on
Commit
8a73406
·
1 Parent(s): 4a3d5c5

root route setup for api

Browse files
Files changed (6) hide show
  1. .air.toml +1 -1
  2. .gitattributes +0 -2
  3. docker-compose.yml +4 -4
  4. go.mod +8 -1
  5. go.sum +15 -0
  6. main.go +170 -21
.air.toml CHANGED
@@ -15,7 +15,7 @@ tmp_dir = "tmp"
15
  full_bin = ""
16
  include_dir = []
17
  include_ext = ["go", "tpl", "tmpl", "html"]
18
- include_file = []
19
  kill_delay = "0s"
20
  log = "build-errors.log"
21
  poll = false
 
15
  full_bin = ""
16
  include_dir = []
17
  include_ext = ["go", "tpl", "tmpl", "html"]
18
+ include_file = [".env"]
19
  kill_delay = "0s"
20
  log = "build-errors.log"
21
  poll = false
.gitattributes DELETED
@@ -1,2 +0,0 @@
1
- tmp/niyam filter=lfs diff=lfs merge=lfs -text
2
- tmp/main filter=lfs diff=lfs merge=lfs -text
 
 
 
docker-compose.yml CHANGED
@@ -8,12 +8,12 @@ services:
8
  dockerfile: Dockerfile.dev # Refers to your main Dockerfile
9
  target: builder # IMPORTANT: Build only the 'builder' stage for dev
10
  ports:
11
- - "7860:7860" # Map host port 8080 to container port 8080
12
  volumes:
13
  - .:/app # Mount your current host directory into /app inside the container
14
- environment:
15
- # Any environment variables your app needs for dev
16
- - PORT=7860
17
  command: air # Run the 'air' command, which watches files and rebuilds
18
  # Optional: If you need to specify the main Go file path for air,
19
  # you might need an .air.toml config file, or adjust the command.
 
8
  dockerfile: Dockerfile.dev # Refers to your main Dockerfile
9
  target: builder # IMPORTANT: Build only the 'builder' stage for dev
10
  ports:
11
+ - "${PORT}:${PORT}"
12
  volumes:
13
  - .:/app # Mount your current host directory into /app inside the container
14
+ # environment:
15
+ # Any environment variables your app needs for dev
16
+ # - PORT=7860
17
  command: air # Run the 'air' command, which watches files and rebuilds
18
  # Optional: If you need to specify the main Go file path for air,
19
  # you might need an .air.toml config file, or adjust the command.
go.mod CHANGED
@@ -1,13 +1,20 @@
1
- module github.com/axyut/lawops
2
 
3
  go 1.24.2
4
 
5
  require (
6
  github.com/danielgtaylor/huma/v2 v2.32.0
7
  github.com/go-chi/chi/v5 v5.1.0
 
 
8
  )
9
 
10
  require (
11
  github.com/fxamacker/cbor/v2 v2.7.0 // indirect
 
 
 
12
  github.com/x448/float16 v0.8.4 // indirect
 
 
13
  )
 
1
+ module github.com/axyut/niyamAPI
2
 
3
  go 1.24.2
4
 
5
  require (
6
  github.com/danielgtaylor/huma/v2 v2.32.0
7
  github.com/go-chi/chi/v5 v5.1.0
8
+ github.com/joho/godotenv v1.5.1
9
+ github.com/shirou/gopsutil v3.21.11+incompatible
10
  )
11
 
12
  require (
13
  github.com/fxamacker/cbor/v2 v2.7.0 // indirect
14
+ github.com/go-ole/go-ole v1.2.6 // indirect
15
+ github.com/tklauser/go-sysconf v0.3.15 // indirect
16
+ github.com/tklauser/numcpus v0.10.0 // indirect
17
  github.com/x448/float16 v0.8.4 // indirect
18
+ github.com/yusufpapurcu/wmi v1.2.4 // indirect
19
+ golang.org/x/sys v0.31.0 // indirect
20
  )
go.sum CHANGED
@@ -6,13 +6,28 @@ github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv
6
  github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
7
  github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
8
  github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
 
 
9
  github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
10
  github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 
 
11
  github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
12
  github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 
 
13
  github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
14
  github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 
 
 
 
15
  github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
16
  github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
 
 
 
 
 
17
  gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
18
  gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 
6
  github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
7
  github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
8
  github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
9
+ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
10
+ github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
11
  github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
12
  github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
13
+ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
14
+ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
15
  github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
16
  github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
17
+ github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
18
+ github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
19
  github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
20
  github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
21
+ github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4=
22
+ github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4=
23
+ github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso=
24
+ github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ=
25
  github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
26
  github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
27
+ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
28
+ github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
29
+ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
30
+ golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
31
+ golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
32
  gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
33
  gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
main.go CHANGED
@@ -3,46 +3,195 @@ package main
3
  import (
4
  "context"
5
  "fmt"
 
 
6
  "net/http"
 
 
 
 
7
 
8
  "github.com/danielgtaylor/huma/v2"
9
  "github.com/danielgtaylor/huma/v2/adapters/humachi"
10
  "github.com/go-chi/chi/v5"
 
 
 
11
 
12
  _ "github.com/danielgtaylor/huma/v2/formats/cbor"
13
  )
14
 
15
- // GreetingOutput represents the greeting operation response.
16
- type GreetingOutput struct {
 
17
  Body struct {
18
- Message string `json:"message" example:"Hello, world!" doc:"Greeting message"`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  }
20
  }
21
 
22
  func main() {
23
- // Create a new router & API.
 
 
 
 
 
 
 
 
 
 
 
 
24
  router := chi.NewMux()
25
- api := humachi.New(router, huma.DefaultConfig("My API", "1.0.0"))
26
-
27
- // Register GET /greeting/{name} handler.
28
- huma.Get(api, "/greeting/{name}", func(ctx context.Context, input *struct {
29
- Name string `path:"name" maxLength:"30" example:"world" doc:"Name to greet"`
30
- }) (*GreetingOutput, error) {
31
- resp := &GreetingOutput{}
32
- resp.Body.Message = fmt.Sprintf("Hello, %s!", input.Name)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  return resp, nil
34
  })
35
 
36
- port := "7860" // Default port
37
- // You might read the port from an environment variable for flexibility
38
- // if p := os.Getenv("PORT"); p != "" {
39
- // port = p
40
- // }
 
 
41
 
42
- addr := fmt.Sprintf(":%s", port) // Listen on all interfaces
43
- fmt.Printf("Server starting on http://localhost%s/docs\n", addr)
44
- err := http.ListenAndServe(addr, router) // <--- THIS IS KEY
45
  if err != nil {
46
- fmt.Printf("Server failed to start: %v\n", err)
47
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
 
3
  import (
4
  "context"
5
  "fmt"
6
+ "log"
7
+ "net"
8
  "net/http"
9
+ "os"
10
+ "os/signal"
11
+ "syscall"
12
+ "time"
13
 
14
  "github.com/danielgtaylor/huma/v2"
15
  "github.com/danielgtaylor/huma/v2/adapters/humachi"
16
  "github.com/go-chi/chi/v5"
17
+ "github.com/go-chi/chi/v5/middleware"
18
+ "github.com/joho/godotenv"
19
+ "github.com/shirou/gopsutil/cpu"
20
 
21
  _ "github.com/danielgtaylor/huma/v2/formats/cbor"
22
  )
23
 
24
+ var startTime = time.Now()
25
+
26
+ type MetadataOutput struct {
27
  Body struct {
28
+ Service string `json:"service" example:"My API"`
29
+ Version string `json:"version" example:"v1"`
30
+ Description string `json:"description" example:"API description"`
31
+ Status string `json:"status" example:"operational"`
32
+ Uptime string `json:"uptime" example:"8d 19h 16m"`
33
+ Health HealthStatus `json:"health"`
34
+ Documentation string `json:"documentation" example:"/docs"`
35
+ Links MetadataLinks `json:"links"`
36
+ Contact MetadataContact `json:"contact"`
37
+ Environment string `json:"environment" example:"development"`
38
+ }
39
+ }
40
+
41
+ type HealthStatus struct {
42
+ Database string `json:"database" example:"ok"`
43
+ Server string `json:"server" example:"ok"`
44
+ Load float64 `json:"load" example:"11.35"`
45
+ }
46
+
47
+ type MetadataLinks struct {
48
+ Self string `json:"self" example:"/"`
49
+ PrivacyPolicy string `json:"privacyPolicy" example:"/api/terms_condition"`
50
+ }
51
+
52
+ type MetadataContact struct {
53
+ Name string `json:"name" example:"API Support"`
54
+ Email string `json:"email" example:"mail@achyutkoirala.com.np"`
55
+ URL string `json:"url" example:"/contact"`
56
+ }
57
+
58
+ type HealthCheckOutput struct {
59
+ Body struct {
60
+ Status string `json:"status" example:"healthy" doc:"API health status"`
61
  }
62
  }
63
 
64
  func main() {
65
+ err := godotenv.Load()
66
+ if err != nil {
67
+ log.Printf("No .env file found or error loading .env: %v. Proceeding without it.", err)
68
+ } else {
69
+ log.Println(".env file loaded successfully.")
70
+ }
71
+
72
+ port := os.Getenv("PORT")
73
+ if port == "" {
74
+ port = "8080" // Default port if not specified in .env or actual environment
75
+ }
76
+ listenAddr := ":" + port
77
+
78
  router := chi.NewMux()
79
+
80
+ // chi middleware for request logging and panic recovery
81
+ router.Use(middleware.Logger)
82
+ router.Use(middleware.Recoverer) // recover from panics, prevents server crash
83
+
84
+ // Store the Huma config in a variable to access its fields
85
+ apiConfig := huma.DefaultConfig("Niyam API", "1.0.0")
86
+ apiConfig.Info.Description = "API/Backend service for the Niyam application."
87
+ api := humachi.New(router, apiConfig) // Pass the config variable here
88
+
89
+ // Root endpoint "/"
90
+ huma.Get(api, "/", func(ctx context.Context, input *struct{}) (*MetadataOutput, error) {
91
+ // Log.Println for specific handler entry/exit is now less critical with middleware.Logger
92
+ // log.Println("Received root metadata request.")
93
+
94
+ // Calculate uptime
95
+ uptimeDuration := time.Since(startTime)
96
+ uptime := fmt.Sprintf("%dd %dh %dm",
97
+ int(uptimeDuration.Hours()/24),
98
+ int(uptimeDuration.Hours())%24,
99
+ int(uptimeDuration.Minutes())%60,
100
+ )
101
+
102
+ // Get actual CPU load percentage
103
+ cpuPercentages, err := cpu.Percent(time.Second, false)
104
+ var currentLoad float64
105
+ if err != nil {
106
+ log.Printf("Error getting CPU load: %v", err)
107
+ currentLoad = -1.0 // Indicate error or unknown load
108
+ } else if len(cpuPercentages) > 0 {
109
+ currentLoad = cpuPercentages[0] // Get the overall CPU percentage
110
+ } else {
111
+ currentLoad = 0.0 // No CPU data or 0%
112
+ }
113
+
114
+ resp := &MetadataOutput{}
115
+ resp.Body.Service = apiConfig.Info.Title
116
+ resp.Body.Version = apiConfig.Info.Version
117
+ resp.Body.Description = apiConfig.Info.Description
118
+ resp.Body.Status = "operational"
119
+ resp.Body.Uptime = uptime
120
+ resp.Body.Health = HealthStatus{
121
+ Database: "ok", // TODO: check DB connection
122
+ Server: "ok",
123
+ Load: currentLoad,
124
+ }
125
+ resp.Body.Documentation = "/docs"
126
+
127
+ resp.Body.Links = MetadataLinks{
128
+ Self: "/",
129
+ PrivacyPolicy: "/api/terms_and_condition",
130
+ }
131
+
132
+ resp.Body.Contact = MetadataContact{
133
+ Name: "API Support",
134
+ Email: "mail@achyutkoirala.com.np",
135
+ URL: "/contact",
136
+ }
137
+
138
+ // Get environment from an ENV var, default to "development"
139
+ appEnv := os.Getenv("APP_ENV")
140
+ if appEnv == "" {
141
+ appEnv = "development"
142
+ }
143
+ resp.Body.Environment = appEnv
144
+
145
  return resp, nil
146
  })
147
 
148
+ // Health Check Endpoint
149
+ huma.Get(api, "/healthz", func(ctx context.Context, input *struct{}) (*HealthCheckOutput, error) {
150
+ // log.Println("Health check requested.") // Logger middleware will cover this.
151
+ resp := &HealthCheckOutput{}
152
+ resp.Body.Status = "healthy"
153
+ return resp, nil
154
+ })
155
 
156
+ // Dynamically Get the Actual Host and Port (for logging)
157
+ listener, err := net.Listen("tcp", listenAddr)
 
158
  if err != nil {
159
+ log.Fatalf("Server failed to listen on %s: %v", listenAddr, err)
160
  }
161
+ defer listener.Close()
162
+
163
+ actualAddr := listener.Addr().String()
164
+ log.Printf("Server listening on %s (Access docs at http://localhost%s/docs)\n", actualAddr, listener.Addr().String())
165
+
166
+ // Graceful Shutdown
167
+ quit := make(chan os.Signal, 1)
168
+ signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
169
+
170
+ srv := &http.Server{
171
+ Addr: listener.Addr().String(),
172
+ Handler: router,
173
+ ReadTimeout: 5 * time.Second,
174
+ WriteTimeout: 10 * time.Second,
175
+ IdleTimeout: 120 * time.Second,
176
+ }
177
+
178
+ go func() {
179
+ log.Printf("Starting HTTP server on %s...", srv.Addr)
180
+ if err := srv.Serve(listener); err != nil && err != http.ErrServerClosed {
181
+ log.Fatalf("HTTP server failed: %v", err)
182
+ }
183
+ log.Println("HTTP server stopped.")
184
+ }()
185
+
186
+ sig := <-quit
187
+ log.Printf("Received signal '%s'. Shutting down server...", sig)
188
+
189
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
190
+ defer cancel()
191
+
192
+ if err := srv.Shutdown(ctx); err != nil {
193
+ log.Fatalf("Server shutdown failed: %v", err)
194
+ }
195
+
196
+ log.Println("Server gracefully shut down.")
197
  }