Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 32 additions & 26 deletions .github/workflows/workflow.yml
Original file line number Diff line number Diff line change
@@ -1,55 +1,61 @@
name: Go Build and Test with Database
name: User Service CI

on:
push:
branches:
- "master"
- "develop"
- master
- develop
pull_request:
branches:
- "master"
- "develop"
- master
- develop

jobs:
test:
runs-on: ubuntu-latest

services:
postgres:
image: postgres:15
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: testdb
POSTGRES_DB: userdb
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d testdb"
--health-interval 5s
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- name: Checkout Code
- name: Checkout Repository
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
uses: actions/setup-go@v4
with:
go-version: 1.23.4
go-version: 1.21

- name: Install Dependencies
run: go mod tidy

- name: Wait for PostgreSQL to Start
run: sleep 10 # Ensure PostgreSQL is fully initialized

- name: Create .env file for testing

- name: Create .env file for Testing
run: |
echo "DB_USER=postgres" > .env
echo "DB_PASSWORD=postgres" >> .env
echo "DB_NAME=testdb" >> .env
echo "JWT_SECRET=testsecret" > .env
echo "DB_HOST=localhost" >> .env
echo "DB_PORT=5432" >> .env

- name: Run Tests with Database
run: go test -v ./...
echo "DB_USER=postgres" >> .env
echo "DB_PASSWORD=postgres" >> .env
echo "DB_NAME=userdb" >> .env

- name: Run Tests
env:
JWT_SECRET: testsecret
DB_HOST: localhost
DB_PORT: 5432
DB_USER: postgres
DB_PASSWORD: postgres
DB_NAME: userdb
run: go test ./tests/ -v
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM golang:1.21

WORKDIR /app

COPY go.mod ./
COPY go.sum ./
RUN go mod tidy

COPY . .

RUN go build -o main ./cmd/main.go

CMD ["./main"]
5 changes: 3 additions & 2 deletions main.go → cmd/main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package main
package cmd

import (
"user_crud/config"
"user_crud/routes"

"github.com/gin-gonic/gin"
)
Expand All @@ -13,7 +14,7 @@ func main() {
config.ConnectDB()

// Register Routes
UserRoutes(r)
routes.UserRoutes(r)

// Start Server
r.Run(":8080")
Expand Down
11 changes: 6 additions & 5 deletions controllers/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"net/http"
"strconv"
"user_crud/models"
"user_crud/repository"
"user_crud/utils"

"github.com/gin-gonic/gin"
Expand All @@ -17,7 +18,7 @@ func CreateUser(c *gin.Context) {
return
}

err := models.CreateUser(user)
err := repository.CreateUser(user)
if err != nil {
utils.SendErrorResponse(c, http.StatusInternalServerError, "Failed to create user")
return
Expand All @@ -28,7 +29,7 @@ func CreateUser(c *gin.Context) {

// Get All Users
func GetAllUsers(c *gin.Context) {
users, err := models.GetAllUsers()
users, err := repository.GetAllUsers()
if err != nil {
utils.SendErrorResponse(c, http.StatusInternalServerError, "Failed to fetch users")
return
Expand All @@ -39,7 +40,7 @@ func GetAllUsers(c *gin.Context) {
// Get User by ID
func GetUserByID(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
user, err := models.GetUserByID(id)
user, err := repository.GetUserByID(id)
if err != nil {
utils.SendErrorResponse(c, http.StatusNotFound, "User not found")
return
Expand All @@ -55,7 +56,7 @@ func UpdateUser(c *gin.Context) {
return
}

err := models.UpdateUser(user)
err := repository.UpdateUser(user)
if err != nil {
utils.SendErrorResponse(c, http.StatusInternalServerError, "Failed to update user")
return
Expand All @@ -68,7 +69,7 @@ func UpdateUser(c *gin.Context) {
func DeleteUser(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))

err := models.DeleteUser(id)
err := repository.DeleteUser(id)
if err != nil {
utils.SendErrorResponse(c, http.StatusInternalServerError, "Failed to delete user")
return
Expand Down
20 changes: 20 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version: "3.8"

services:
user-service:
build: .
ports:
- "8080:8080"
depends_on:
- db
env_file:
- .env

db:
image: postgres:15
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: userdb
ports:
- "5432:5432"
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ module user_crud
go 1.23.4

require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/gin-gonic/gin v1.10.0
github.com/joho/godotenv v1.5.1
github.com/lib/pq v1.10.9
golang.org/x/time v0.10.0
)

require (
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
Expand Down Expand Up @@ -80,6 +82,8 @@ golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
Expand Down
21 changes: 21 additions & 0 deletions middleware/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package middleware

import (
"net/http"
"user_crud/utils"

"github.com/gin-gonic/gin"
)

// AuthMiddleware - Protect routes with JWT authentication
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" || !utils.ValidateJWT(token) {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
c.Abort()
return
}
c.Next()
}
}
18 changes: 18 additions & 0 deletions middleware/ratelimit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package middleware

import (
"github.com/gin-gonic/gin"
"golang.org/x/time/rate"
)

var limiter = rate.NewLimiter(1, 5) // 1 request per second, burst up to 5

func RateLimit() gin.HandlerFunc {
return func(c *gin.Context) {
if !limiter.Allow() {
c.AbortWithStatusJSON(429, gin.H{"error": "Too many requests"})
return
}
c.Next()
}
}
48 changes: 0 additions & 48 deletions models/user.go
Original file line number Diff line number Diff line change
@@ -1,56 +1,8 @@
package models

import "user_crud/config"

type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}

// Create User
func CreateUser(user User) error {
_, err := config.DB.Exec("INSERT INTO users (name, email, age) VALUES ($1, $2, $3)", user.Name, user.Email, user.Age)
return err
}

// Get All Users
func GetAllUsers() ([]User, error) {
rows, err := config.DB.Query("SELECT id, name, email, age FROM users")
if err != nil {
return nil, err
}
defer rows.Close()

var users []User
for rows.Next() {
var user User
err := rows.Scan(&user.ID, &user.Name, &user.Email, &user.Age)
if err != nil {
return nil, err
}
users = append(users, user)
}

return users, nil
}

// Get User by ID
func GetUserByID(id int) (User, error) {
var user User
err := config.DB.QueryRow("SELECT id, name, email, age FROM users WHERE id = $1", id).Scan(&user.ID, &user.Name, &user.Email, &user.Age)
return user, err
}

// Update User
func UpdateUser(user User) error {
_, err := config.DB.Exec("UPDATE users SET name=$1, email=$2, age=$3 WHERE id=$4", user.Name, user.Email, user.Age, user.ID)
return err
}

// Delete User
func DeleteUser(id int) error {
_, err := config.DB.Exec("DELETE FROM users WHERE id = $1", id)
return err
}
52 changes: 52 additions & 0 deletions repository/user_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package repository

import (
"user_crud/config"
"user_crud/models"
)

// Create User
func CreateUser(user models.User) error {
_, err := config.DB.Exec("INSERT INTO users (name, email, age) VALUES ($1, $2, $3)", user.Name, user.Email, user.Age)
return err
}

// Get All Users
func GetAllUsers() ([]models.User, error) {
rows, err := config.DB.Query("SELECT id, name, email, age FROM users")
if err != nil {
return nil, err
}
defer rows.Close()

var users []models.User
for rows.Next() {
var user models.User
err := rows.Scan(&user.ID, &user.Name, &user.Email, &user.Age)
if err != nil {
return nil, err
}
users = append(users, user)
}

return users, nil
}

// Get User by ID
func GetUserByID(id int) (models.User, error) {
var user models.User
err := config.DB.QueryRow("SELECT id, name, email, age FROM users WHERE id = $1", id).Scan(&user.ID, &user.Name, &user.Email, &user.Age)
return user, err
}

// Update User
func UpdateUser(user models.User) error {
_, err := config.DB.Exec("UPDATE users SET name=$1, email=$2, age=$3 WHERE id=$4", user.Name, user.Email, user.Age, user.ID)
return err
}

// Delete User
func DeleteUser(id int) error {
_, err := config.DB.Exec("DELETE FROM users WHERE id = $1", id)
return err
}
2 changes: 1 addition & 1 deletion routes.go → routes/routes.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package routes

import (
"user_crud/controllers"
Expand Down
Loading
Loading