Skip to content

Commit 720b7e0

Browse files
committed
feat: init crud routes for user service
1 parent 76e2b31 commit 720b7e0

File tree

8 files changed

+242
-22
lines changed

8 files changed

+242
-22
lines changed

services/user/cmd/server/main.go

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,44 @@ import (
44
"log"
55
"net/http"
66
"os"
7+
"peerprep/user/internal/handlers"
8+
"peerprep/user/internal/repositories"
9+
"peerprep/user/internal/routers"
710
"time"
811

912
"github.com/go-chi/chi/v5"
1013
"github.com/go-chi/chi/v5/middleware"
1114
"go.uber.org/zap"
15+
"gorm.io/driver/postgres"
16+
"gorm.io/gorm"
1217
)
1318

14-
func registerRoutes(r *chi.Mux, logger *zap.Logger) {
15-
// F1.* stubs
16-
r.Post("/api/v1/auth/register", func(w http.ResponseWriter, r *http.Request) {
17-
w.WriteHeader(http.StatusCreated)
18-
w.Write([]byte(`{"status":"registered_stub"}`))
19-
})
20-
r.Post("/api/v1/auth/login", func(w http.ResponseWriter, r *http.Request) {
21-
w.Write([]byte(`{"accessToken":"stub","refreshToken":"stub"}`))
22-
})
23-
r.Post("/api/v1/auth/logout", func(w http.ResponseWriter, r *http.Request) {
24-
w.Write([]byte(`{"status":"logged_out_stub"}`))
25-
})
26-
r.Put("/api/v1/account", func(w http.ResponseWriter, r *http.Request) {
27-
w.Write([]byte(`{"status":"account_updated_stub"}`))
28-
})
29-
r.Delete("/api/v1/account", func(w http.ResponseWriter, r *http.Request) {
30-
w.Write([]byte(`{"status":"account_deleted_stub"}`))
31-
})
32-
}
33-
3419
func main() {
3520
logger, _ := zap.NewProduction()
3621
defer logger.Sync()
3722

23+
// Initialize database connection
24+
dsn := "host=localhost user=your_user password=your_password dbname=your_db port=5432 sslmode=disable"
25+
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
26+
if err != nil {
27+
logger.Fatal("Failed to connect to the database", zap.Error(err))
28+
}
29+
30+
// Initialize repository and handler
31+
userRepo := &repositories.UserRepository{DB: db}
32+
userHandler := &handlers.UserHandler{Repo: userRepo}
33+
34+
// Set up router
3835
r := chi.NewRouter()
3936
r.Use(middleware.RequestID, middleware.RealIP, middleware.Logger, middleware.Recoverer, middleware.Timeout(60*time.Second))
4037

38+
// Health check route
4139
r.Get("/healthz", func(w http.ResponseWriter, _ *http.Request) { w.Write([]byte("ok")) })
42-
registerRoutes(r, logger)
4340

41+
// Register user routes
42+
routers.UserRoutes(r, userHandler)
43+
44+
// Start server
4445
port := os.Getenv("PORT")
4546
if port == "" {
4647
port = "8080"

services/user/go.mod

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,17 @@ require (
77
go.uber.org/zap v1.27.0
88
)
99

10-
require go.uber.org/multierr v1.10.0 // indirect
10+
require (
11+
github.com/jackc/pgpassfile v1.0.0 // indirect
12+
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
13+
github.com/jackc/pgx/v5 v5.6.0 // indirect
14+
github.com/jackc/puddle/v2 v2.2.2 // indirect
15+
github.com/jinzhu/inflection v1.0.0 // indirect
16+
github.com/jinzhu/now v1.1.5 // indirect
17+
go.uber.org/multierr v1.10.0 // indirect
18+
golang.org/x/crypto v0.31.0 // indirect
19+
golang.org/x/sync v0.10.0 // indirect
20+
golang.org/x/text v0.21.0 // indirect
21+
gorm.io/driver/postgres v1.6.0 // indirect
22+
gorm.io/gorm v1.31.0 // indirect
23+
)

services/user/go.sum

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
23
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
34
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
45
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
6+
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
7+
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
8+
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
9+
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
10+
github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY=
11+
github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw=
12+
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
13+
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
14+
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
15+
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
16+
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
17+
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
518
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
619
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
20+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
21+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
22+
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
723
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
824
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
925
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
@@ -12,5 +28,19 @@ go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
1228
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
1329
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
1430
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
31+
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
32+
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
33+
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
34+
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
35+
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
36+
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
37+
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
38+
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
39+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
40+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
1541
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
1642
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
43+
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
44+
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
45+
gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY=
46+
gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=

services/user/internal/handlers/auth_handler.go

Whitespace-only changes.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package handlers
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"peerprep/user/internal/models"
7+
"peerprep/user/internal/repositories"
8+
)
9+
10+
type UserHandler struct {
11+
Repo *repositories.UserRepository
12+
}
13+
14+
// CreateUserHandler handles user creation
15+
func (h *UserHandler) CreateUserHandler(w http.ResponseWriter, r *http.Request) {
16+
var user models.User
17+
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
18+
http.Error(w, "Invalid request payload", http.StatusBadRequest)
19+
return
20+
}
21+
22+
// Save user using the repository
23+
if err := h.Repo.CreateUser(&user); err != nil {
24+
http.Error(w, "Failed to create user", http.StatusInternalServerError)
25+
return
26+
}
27+
28+
w.WriteHeader(http.StatusCreated)
29+
json.NewEncoder(w).Encode(user)
30+
}
31+
32+
// GetUserHandler retrieves a user by ID
33+
func (h *UserHandler) GetUserHandler(w http.ResponseWriter, r *http.Request) {
34+
userID := r.URL.Query().Get("id")
35+
if userID == "" {
36+
http.Error(w, "User ID is required", http.StatusBadRequest)
37+
return
38+
}
39+
40+
user, err := h.Repo.GetUserByID(userID)
41+
if err != nil {
42+
if err == repositories.ErrUserNotFound {
43+
http.Error(w, "User not found", http.StatusNotFound)
44+
} else {
45+
http.Error(w, "Failed to retrieve user", http.StatusInternalServerError)
46+
}
47+
return
48+
}
49+
50+
json.NewEncoder(w).Encode(user)
51+
}
52+
53+
// UpdateUserHandler updates user details
54+
func (h *UserHandler) UpdateUserHandler(w http.ResponseWriter, r *http.Request) {
55+
userID := r.URL.Query().Get("id")
56+
if userID == "" {
57+
http.Error(w, "User ID is required", http.StatusBadRequest)
58+
return
59+
}
60+
61+
var updates models.User
62+
if err := json.NewDecoder(r.Body).Decode(&updates); err != nil {
63+
http.Error(w, "Invalid request payload", http.StatusBadRequest)
64+
return
65+
}
66+
67+
user, err := h.Repo.UpdateUser(userID, &updates)
68+
if err != nil {
69+
if err == repositories.ErrUserNotFound {
70+
http.Error(w, "User not found", http.StatusNotFound)
71+
} else {
72+
http.Error(w, "Failed to update user", http.StatusInternalServerError)
73+
}
74+
return
75+
}
76+
77+
json.NewEncoder(w).Encode(user)
78+
}
79+
80+
// DeleteUserHandler deletes a user by ID
81+
func (h *UserHandler) DeleteUserHandler(w http.ResponseWriter, r *http.Request) {
82+
userID := r.URL.Query().Get("id")
83+
if userID == "" {
84+
http.Error(w, "User ID is required", http.StatusBadRequest)
85+
return
86+
}
87+
88+
if err := h.Repo.DeleteUser(userID); err != nil {
89+
if err == repositories.ErrUserNotFound {
90+
http.Error(w, "User not found", http.StatusNotFound)
91+
} else {
92+
http.Error(w, "Failed to delete user", http.StatusInternalServerError)
93+
}
94+
return
95+
}
96+
97+
w.WriteHeader(http.StatusNoContent)
98+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package models
2+
3+
import (
4+
"gorm.io/gorm"
5+
)
6+
7+
// User represents a registered user in the system.
8+
type User struct {
9+
gorm.Model
10+
Username string `gorm:"unique;not null" json:"username"`
11+
Email string `gorm:"unique;not null" json:"email"`
12+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package repositories
2+
3+
import (
4+
"errors"
5+
"peerprep/user/internal/models"
6+
7+
"gorm.io/gorm"
8+
)
9+
10+
var ErrUserNotFound = errors.New("user not found")
11+
12+
type UserRepository struct {
13+
DB *gorm.DB
14+
}
15+
16+
func (r *UserRepository) CreateUser(user *models.User) error {
17+
return r.DB.Create(user).Error
18+
}
19+
20+
func (r *UserRepository) GetUserByID(userID string) (*models.User, error) {
21+
var user models.User
22+
err := r.DB.First(&user, "user_id = ?", userID).Error
23+
if errors.Is(err, gorm.ErrRecordNotFound) {
24+
return nil, ErrUserNotFound
25+
}
26+
return &user, err
27+
}
28+
29+
func (r *UserRepository) UpdateUser(userID string, updates *models.User) (*models.User, error) {
30+
var user models.User
31+
if err := r.DB.First(&user, "user_id = ?", userID).Error; err != nil {
32+
if errors.Is(err, gorm.ErrRecordNotFound) {
33+
return nil, ErrUserNotFound
34+
}
35+
return nil, err
36+
}
37+
38+
if err := r.DB.Model(&user).Updates(updates).Error; err != nil {
39+
return nil, err
40+
}
41+
return &user, nil
42+
}
43+
44+
func (r *UserRepository) DeleteUser(userID string) error {
45+
result := r.DB.Delete(&models.User{}, "user_id = ?", userID)
46+
if result.RowsAffected == 0 {
47+
return ErrUserNotFound
48+
}
49+
return result.Error
50+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package routers
2+
3+
import (
4+
handlers "peerprep/user/internal/handlers"
5+
6+
"github.com/go-chi/chi/v5"
7+
)
8+
9+
func UserRoutes(r *chi.Mux, userHandler *handlers.UserHandler) {
10+
r.Route("/api/v1/users", func(r chi.Router) {
11+
r.Post("/", userHandler.CreateUserHandler) // Create user
12+
r.Get("/", userHandler.GetUserHandler) // Get user by ID
13+
r.Put("/", userHandler.UpdateUserHandler) // Update user
14+
r.Delete("/", userHandler.DeleteUserHandler) // Delete user
15+
})
16+
}

0 commit comments

Comments
 (0)