Skip to content

Commit bdc2e2e

Browse files
feat: refactor (#7)
Co-authored-by: Abhik Ray <abhikaboy@gmail.com>
1 parent d67adef commit bdc2e2e

File tree

32 files changed

+740
-454
lines changed

32 files changed

+740
-454
lines changed

backend/.golangci.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
linters-settings:
2+
# from https://betterstack.com/community/guides/logging/logging-in-go/#best-practices-for-writing-and-storing-go-logs
3+
sloglint:
4+
# Enforce not mixing key-value pairs and attributes.
5+
# Default: true
6+
no-mixed-args: false
7+
# Enforce using key-value pairs only (overrides no-mixed-args, incompatible with attr-only).
8+
# Default: false
9+
kv-only: true
10+
# Enforce using attributes only (overrides no-mixed-args, incompatible with kv-only).
11+
# Default: false
12+
attr-only: true
13+
# Enforce using methods that accept a context.
14+
# Default: ""
15+
context: all
16+
# Enforce using static values for log messages.
17+
# Default: false
18+
static-msg: true
19+
# Enforce using constants instead of raw keys.
20+
# Default: false
21+
no-raw-keys: true
22+
# Enforce a single key naming convention.
23+
# Values: snake, kebab, camel, pascal
24+
# Default: ""
25+
key-naming-case: snake
26+
# Enforce putting arguments on separate lines.
27+
# Default: false
28+
args-on-sep-lines: true

backend/cmd/db/example/main.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"flag"
6+
"log/slog"
7+
"os"
8+
9+
"github.com/GenerateNU/platemate/internal/config"
10+
"github.com/GenerateNU/platemate/internal/storage/mongo"
11+
"github.com/GenerateNU/platemate/internal/xslog"
12+
"github.com/joho/godotenv"
13+
)
14+
15+
/*
16+
Adds two example fields to the collection passed via
17+
the collection flag.
18+
Example usage: go run cmd/db/example/main.go -collection=collectionName
19+
Applies empty strings as default values
20+
*/
21+
func main() {
22+
ctx := context.Background()
23+
collection := flag.String("collection", "users", "collection to add example fields to")
24+
flag.Parse()
25+
if *collection == "" {
26+
fatal(ctx, "collection flag is required", nil)
27+
}
28+
29+
if err := godotenv.Load(); err != nil {
30+
fatal(ctx, "Failed to load .env", err)
31+
}
32+
config, err := config.Load()
33+
if err != nil {
34+
fatal(ctx, "Failed to load config", err)
35+
}
36+
37+
db, err := mongo.New(ctx, config.Atlas)
38+
if err != nil {
39+
fatal(ctx, "Failed to connect to MongoDB", err)
40+
}
41+
42+
if err := db.CreateExampleFields(ctx, *collection); err != nil {
43+
fatal(ctx, "Failed to add example fields", err)
44+
}
45+
}
46+
47+
func fatal(ctx context.Context, msg string, err error) {
48+
slog.LogAttrs(
49+
ctx,
50+
slog.LevelError,
51+
msg,
52+
xslog.Error(err),
53+
)
54+
os.Exit(1)
55+
}

backend/cmd/server/main.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"flag"
6+
"fmt"
7+
"io"
8+
"log/slog"
9+
"os"
10+
"os/signal"
11+
"syscall"
12+
13+
"github.com/GenerateNU/platemate/internal/config"
14+
"github.com/GenerateNU/platemate/internal/server"
15+
"github.com/GenerateNU/platemate/internal/storage/mongo"
16+
"github.com/GenerateNU/platemate/internal/xslog"
17+
"github.com/joho/godotenv"
18+
)
19+
20+
func main() {
21+
run(os.Stderr, os.Args[1:])
22+
}
23+
24+
func run(stderr io.Writer, args []string) {
25+
cmd := flag.NewFlagSet("", flag.ExitOnError)
26+
verboseFlag := cmd.Bool("v", false, "")
27+
logLevelFlag := cmd.String("log-level", slog.LevelDebug.String(), "")
28+
if err := cmd.Parse(args); err != nil {
29+
fmt.Fprint(stderr, err)
30+
os.Exit(1)
31+
}
32+
logger := newLogger(*logLevelFlag, *verboseFlag, stderr)
33+
slog.SetDefault(logger)
34+
35+
ctx := context.Background()
36+
37+
if err := godotenv.Load(); err != nil {
38+
fatal(ctx, "Failed to load .env", err)
39+
}
40+
41+
config, err := config.Load()
42+
if err != nil {
43+
fatal(ctx, "Failed to load config", err)
44+
}
45+
46+
db, err := mongo.New(ctx, config.Atlas)
47+
if err != nil {
48+
fatal(ctx, "Failed to connect to MongoDB", err)
49+
}
50+
51+
app := server.New(db.Collections)
52+
53+
go func() {
54+
if err := app.Listen(":" + config.App.Port); err != nil {
55+
fatal(ctx, "Failed to start server", err)
56+
}
57+
}()
58+
59+
quit := make(chan os.Signal, 1)
60+
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
61+
62+
<-quit
63+
slog.LogAttrs(
64+
ctx,
65+
slog.LevelInfo,
66+
"Stopping server",
67+
)
68+
69+
if err := app.Shutdown(); err != nil {
70+
fatal(ctx, "Failed to shutdown server", err)
71+
}
72+
73+
slog.LogAttrs(
74+
ctx,
75+
slog.LevelInfo,
76+
"Server shutdown",
77+
)
78+
}
79+
80+
func newLogger(logLevel string, verbose bool, stderr io.Writer) *slog.Logger {
81+
if verbose {
82+
logLevel = "debug"
83+
}
84+
level := slog.LevelInfo.Level()
85+
switch logLevel {
86+
case "debug":
87+
level = slog.LevelDebug.Level()
88+
case "warn":
89+
level = slog.LevelWarn.Level()
90+
case "error":
91+
level = slog.LevelError.Level()
92+
}
93+
return slog.New(slog.NewJSONHandler(stderr, &slog.HandlerOptions{
94+
AddSource: logLevel == "debug",
95+
Level: level,
96+
}))
97+
}
98+
99+
func fatal(ctx context.Context, msg string, err error) {
100+
slog.LogAttrs(
101+
ctx,
102+
slog.LevelError,
103+
msg,
104+
xslog.Error(err),
105+
)
106+
os.Exit(1)
107+
}

backend/cmd/server/main_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"io"
6+
"net/http"
7+
"path/filepath"
8+
"testing"
9+
10+
"github.com/GenerateNU/platemate/internal/config"
11+
"github.com/GenerateNU/platemate/internal/server"
12+
"github.com/GenerateNU/platemate/internal/storage/mongo"
13+
"github.com/gofiber/fiber/v2"
14+
"github.com/joho/godotenv"
15+
"github.com/stretchr/testify/assert"
16+
)
17+
18+
func TestIndexRoute(t *testing.T) {
19+
t.Parallel()
20+
tests := []struct {
21+
name string
22+
desc string
23+
route string
24+
expectedError bool
25+
expectedCode int
26+
expectedBody string
27+
}{
28+
{
29+
name: "index",
30+
desc: "test index route",
31+
route: "/",
32+
expectedError: false,
33+
expectedCode: 200,
34+
expectedBody: "Welcome to PlateMate!",
35+
},
36+
}
37+
38+
app := setup(t)
39+
40+
for _, tt := range tests {
41+
t.Run(tt.name, func(t *testing.T) {
42+
t.Parallel()
43+
req, err := http.NewRequest(
44+
http.MethodGet,
45+
tt.route,
46+
nil,
47+
)
48+
assert.NoErrorf(t, err, tt.desc)
49+
50+
// Perform the request plain with the app. The -1 disables request latency.
51+
res, err := app.Test(req, -1)
52+
if !tt.expectedError {
53+
assert.NoErrorf(t, err, tt.desc)
54+
}
55+
56+
// As expected errors lead to broken responses, the next test case needs to be processed.
57+
if tt.expectedError {
58+
return
59+
}
60+
61+
assert.Equalf(t, tt.expectedCode, res.StatusCode, tt.desc)
62+
63+
body, err := io.ReadAll(res.Body)
64+
assert.NoErrorf(t, err, tt.desc)
65+
assert.Equalf(t, tt.expectedBody, string(body), tt.desc)
66+
})
67+
}
68+
69+
t.Cleanup(func() {
70+
if err := app.Shutdown(); err != nil {
71+
t.Fatalf("Failed to shutdown server: %v", err)
72+
}
73+
})
74+
}
75+
76+
func setup(t *testing.T) *fiber.App {
77+
t.Helper()
78+
if err := godotenv.Load(filepath.Join("..", "..", ".env")); err != nil {
79+
t.Fatal("Failed to load .env")
80+
}
81+
cfg := config.Atlas{
82+
User: "test",
83+
Pass: "platemate-test-pw",
84+
Cluster: "Development",
85+
Environment: "Test",
86+
}
87+
db, err := mongo.New(context.Background(), cfg)
88+
if err != nil {
89+
t.Fatalf("Failed to connect to MongoDB: %v", err)
90+
}
91+
return server.New(db.Collections)
92+
}

backend/errs/http.go

Lines changed: 0 additions & 19 deletions
This file was deleted.

backend/go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ require (
1212

1313
require (
1414
github.com/andybalholm/brotli v1.1.1 // indirect
15+
github.com/caarlos0/env/v11 v11.3.1
1516
github.com/davecgh/go-spew v1.1.1 // indirect
1617
github.com/golang/snappy v0.0.4 // indirect
1718
github.com/google/uuid v1.6.0 // indirect
@@ -30,7 +31,7 @@ require (
3031
github.com/xdg-go/stringprep v1.0.4 // indirect
3132
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
3233
golang.org/x/crypto v0.29.0 // indirect
33-
golang.org/x/sync v0.9.0 // indirect
34+
golang.org/x/sync v0.9.0
3435
golang.org/x/sys v0.28.0 // indirect
3536
golang.org/x/text v0.20.0 // indirect
3637
gopkg.in/yaml.v3 v3.0.1 // indirect

backend/go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
22
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
3+
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
4+
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
35
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
46
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
57
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=

backend/internal/config/app.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package config
2+
3+
type App struct {
4+
Port string `env:"PORT" envDefault:"8080"`
5+
}

backend/internal/config/atlas.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package config
2+
3+
import "fmt"
4+
5+
type Atlas struct {
6+
User string `env:"USER"`
7+
Pass string `env:"PASS"`
8+
Cluster string `env:"CLUSTER"`
9+
Environment string `env:"ENVIRONMENT"`
10+
}
11+
12+
const placeholderURI string = "mongodb+srv://%s:%s@development.t8bgq.mongodb.net/?retryWrites=true&w=majority&appName=%s"
13+
14+
func (a *Atlas) URI() string {
15+
return fmt.Sprintf(placeholderURI, a.User, a.Pass, a.Cluster)
16+
}

backend/internal/config/config.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package config
2+
3+
import "github.com/caarlos0/env/v11"
4+
5+
type Config struct {
6+
App `envPrefix:"APP_"`
7+
Atlas `envPrefix:"ATLAS_"`
8+
}
9+
10+
func Load() (Config, error) {
11+
return env.ParseAs[Config]()
12+
}

0 commit comments

Comments
 (0)