Initial commit

This commit is contained in:
tiff 2024-12-29 14:35:26 -05:00
commit 915e44b69a
10 changed files with 339 additions and 0 deletions

46
.air.toml Normal file
View File

@ -0,0 +1,46 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "./main"
cmd = "make build"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata", "node_modules"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
time = false
[misc]
clean_on_exit = false
[screen]
clear_on_rebuild = false
keep_scroll = true

34
.gitignore vendored Normal file
View File

@ -0,0 +1,34 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with "go test -c"
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
tmp/
# IDE specific files
.vscode
.idea
# .env file
.env
# Project build
main
*templ.go
# OS X generated file
.DS_Store

43
Makefile Normal file
View File

@ -0,0 +1,43 @@
# Simple Makefile for a Go project
# Build the application
all: build test
build:
@echo "Building..."
@go build -o main cmd/api/main.go
# Run the application
run:
@go run cmd/api/main.go
# Test the application
test:
@echo "Testing..."
@go test ./... -v
# Clean the binary
clean:
@echo "Cleaning..."
@rm -f main
# Live Reload
watch:
@if command -v air > /dev/null; then \
air; \
echo "Watching...";\
else \
read -p "Go's 'air' is not installed on your machine. Do you want to install it? [Y/n] " choice; \
if [ "$$choice" != "n" ] && [ "$$choice" != "N" ]; then \
go install github.com/air-verse/air@latest; \
air; \
echo "Watching...";\
else \
echo "You chose not to install air. Exiting..."; \
exit 1; \
fi; \
fi
.PHONY: all build run test clean watch

39
README.md Normal file
View File

@ -0,0 +1,39 @@
# Project go-calc
One Paragraph of project description goes here
## Getting Started
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system.
## MakeFile
Run build make command with tests
```bash
make all
```
Build the application
```bash
make build
```
Run the application
```bash
make run
```
Live reload the application:
```bash
make watch
```
Run the test suite:
```bash
make test
```
Clean up binary from the last build:
```bash
make clean
```

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

@ -0,0 +1,57 @@
package main
import (
"context"
"fmt"
"log"
"net/http"
"os/signal"
"syscall"
"time"
"go-calc/internal/server"
)
func gracefulShutdown(apiServer *http.Server, done chan bool) {
// Create context that listens for the interrupt signal from the OS.
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
// Listen for the interrupt signal.
<-ctx.Done()
log.Println("shutting down gracefully, press Ctrl+C again to force")
// The context is used to inform the server it has 5 seconds to finish
// the request it is currently handling
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := apiServer.Shutdown(ctx); err != nil {
log.Printf("Server forced to shutdown with error: %v", err)
}
log.Println("Server exiting")
// Notify the main goroutine that the shutdown is complete
done <- true
}
func main() {
server := server.NewServer()
// Create a done channel to signal when the shutdown is complete
done := make(chan bool, 1)
// Run graceful shutdown in a separate goroutine
go gracefulShutdown(server, done)
err := server.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
panic(fmt.Sprintf("http server error: %s", err))
}
// Wait for the graceful shutdown to complete
<-done
log.Println("Graceful shutdown complete.")
}

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module go-calc
go 1.23.2
require github.com/joho/godotenv v1.5.1

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=

49
internal/server/routes.go Normal file
View File

@ -0,0 +1,49 @@
package server
import (
"encoding/json"
"log"
"net/http"
)
func (s *Server) RegisterRoutes() http.Handler {
mux := http.NewServeMux()
// Register routes
mux.HandleFunc("/", s.HelloWorldHandler)
// Wrap the mux with CORS middleware
return s.corsMiddleware(mux)
}
func (s *Server) corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Set CORS headers
w.Header().Set("Access-Control-Allow-Origin", "*") // Replace "*" with specific origins if needed
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH")
w.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, X-CSRF-Token")
w.Header().Set("Access-Control-Allow-Credentials", "false") // Set to "true" if credentials are required
// Handle preflight OPTIONS requests
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
// Proceed with the next handler
next.ServeHTTP(w, r)
})
}
func (s *Server) HelloWorldHandler(w http.ResponseWriter, r *http.Request) {
resp := map[string]string{"message": "Hello World"}
jsonResp, err := json.Marshal(resp)
if err != nil {
http.Error(w, "Failed to marshal response", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(jsonResp); err != nil {
log.Printf("Failed to write response: %v", err)
}
}

View File

@ -0,0 +1,31 @@
package server
import (
"io"
"net/http"
"net/http/httptest"
"testing"
)
func TestHandler(t *testing.T) {
s := &Server{}
server := httptest.NewServer(http.HandlerFunc(s.HelloWorldHandler))
defer server.Close()
resp, err := http.Get(server.URL)
if err != nil {
t.Fatalf("error making request to server. Err: %v", err)
}
defer resp.Body.Close()
// Assertions
if resp.StatusCode != http.StatusOK {
t.Errorf("expected status OK; got %v", resp.Status)
}
expected := "{\"message\":\"Hello World\"}"
body, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("error reading response body. Err: %v", err)
}
if expected != string(body) {
t.Errorf("expected response body to be %v; got %v", expected, string(body))
}
}

33
internal/server/server.go Normal file
View File

@ -0,0 +1,33 @@
package server
import (
"fmt"
"net/http"
"os"
"strconv"
"time"
_ "github.com/joho/godotenv/autoload"
)
type Server struct {
port int
}
func NewServer() *http.Server {
port, _ := strconv.Atoi(os.Getenv("PORT"))
NewServer := &Server{
port: port,
}
// Declare Server config
server := &http.Server{
Addr: fmt.Sprintf(":%d", NewServer.port),
Handler: NewServer.RegisterRoutes(),
IdleTimeout: time.Minute,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
}
return server
}