Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
6fdcf04
:rocket: start project
HiIamZeref Mar 26, 2025
8ab7df3
:wrench: refactor: change User model
HiIamZeref Mar 26, 2025
05d95f3
:sparkles: feat: create user repository
HiIamZeref Mar 26, 2025
723bff0
:sparkles: feat: create user service
HiIamZeref Mar 26, 2025
2f099dc
:sparkles: feat: implement user presenters and CreateUser handler
HiIamZeref Mar 26, 2025
1776c16
:construction: feat: create routes package and user route file
HiIamZeref Mar 26, 2025
4496cb1
:bulb: style: add comments to User model
HiIamZeref Mar 26, 2025
f930641
:bulb: style: add comments to User repository
HiIamZeref Mar 26, 2025
d7a0437
:wrench: build: add new .env variable
HiIamZeref Mar 26, 2025
7c41784
:sparkles: feat: finish user handlers, presenters and routes
HiIamZeref Mar 26, 2025
7b5ba20
:sparkles: feat: register User routes in the API
HiIamZeref Mar 26, 2025
3514025
:sparkles: feat: add Points Module
HiIamZeref Mar 27, 2025
dc0547f
:sparkles: feat: change user service to also create points entity on …
HiIamZeref Mar 27, 2025
f1e6b36
:card_file_box: feat: add constraints on Points model
HiIamZeref Mar 27, 2025
d67c021
:loud_sound: feat: add logs on API startup
HiIamZeref Mar 27, 2025
8bf5b41
:bulb: style: add comments to Points structure
HiIamZeref Mar 27, 2025
6918790
:sparkles: :wrench: feat: update points logic
HiIamZeref Mar 28, 2025
69bf09c
:see_no_evil: build: add .gitignore
HiIamZeref Mar 28, 2025
3fbb8ba
:rocket: feat: add FIBER_PORT configuration and update server startup…
HiIamZeref Mar 28, 2025
ebe7dd3
:bug: feat: fix FIBER_PORT import from .env
HiIamZeref Mar 28, 2025
3696d5d
:whale: build: add Dockerfile and docker compose
HiIamZeref Mar 28, 2025
28a97eb
:sparkles: feat: create email service and send email on user register
HiIamZeref Mar 28, 2025
f9bbb8e
:sparkles: feat: use gmail as smtp
HiIamZeref Mar 29, 2025
27102b6
:wrench: build: change .env.example variables
HiIamZeref Mar 31, 2025
b19d1a6
:sparkles: feat: alter leaderboard query to add user full name
HiIamZeref Mar 31, 2025
afeb386
:bug: feat: now properly using user_referral presenter
HiIamZeref Mar 31, 2025
c391d8e
:wrench: refactor: change LeaderboardScore struct
HiIamZeref Mar 31, 2025
9f0e12f
:whale: feat: new docker compose to start frontend app
HiIamZeref Mar 31, 2025
f162008
:fire: refactor: delete one more counter in leaderboard calculation
HiIamZeref Mar 31, 2025
3874df4
:sparkles: feat: add email to leaderboard users and send emails async…
HiIamZeref Apr 1, 2025
f3191c8
:sparkles: feat: validate if user is already registered by email
HiIamZeref Apr 1, 2025
42580f7
:sparkles: feat: add application containers
HiIamZeref Apr 1, 2025
2125d05
Merge branch 'main' of https://github.com/HiIamZeref/felipe-backend-test
HiIamZeref Apr 1, 2025
ac26cd5
Merge branch 'feat/app-containers'
HiIamZeref Apr 1, 2025
316f6b5
:fire: feat: remove frontend from compose
HiIamZeref Apr 1, 2025
58af379
:wrench: build: change port on Dockerfile
HiIamZeref Apr 1, 2025
ba78546
:wrench: build: change port name
HiIamZeref Apr 1, 2025
c003096
:whale: feat: change Dockerfile to build go app
HiIamZeref Apr 1, 2025
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
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
volumes/
15 changes: 15 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Postgres configuration
POSTGRES_HOST=gss-postgres-db
POSTGRES_PORT=5432
POSTGRES_USER=root
POSTGRES_PASSWORD=root
POSTGRES_DB=gss-db

# Fiber configuration
FIBER_PORT=3000

# SMTP configuration
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_EMAIL=
SMTP_PASSWORD=
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.env
.env.production
volumes/
23 changes: 23 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Setting up my base image
FROM golang:1.24.1-alpine

# Set the Current Working Directory inside the container
WORKDIR /app

# Copy go mod and sum files
COPY go.mod go.sum ./

# Get all dependencies
RUN go mod download

# Copy source code
COPY . .

# Build the Go app
RUN go build -o gss-backend ./cmd

# Expose port 3001
EXPOSE 3001

# Run the application
CMD ["./gss-backend"]
8 changes: 8 additions & 0 deletions api/dtos/user_dto.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package dtos

type CreateUserDTO struct {
FullName string `json:"full_name"`
Email string `json:"email"`
PhoneNumber string `json:"phone_number"`
ReferrerCode string `json:"referrer_code"`
}
76 changes: 76 additions & 0 deletions api/handlers/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package handlers

import (
"errors"
"gss-backend/api/dtos"
"gss-backend/api/presenters"
services "gss-backend/pkg/services/user"
"strconv"

"github.com/gofiber/fiber/v2"
)

func CreateUser(user_service services.IUserService) fiber.Handler {
return func(c *fiber.Ctx) error {
var requestBody dtos.CreateUserDTO

err := c.BodyParser(&requestBody)

if err != nil {
c.Status(fiber.StatusBadRequest)
return c.JSON(presenters.UserErrorResponse(err))
}

if requestBody.FullName == "" || requestBody.Email == "" || requestBody.PhoneNumber == "" {
c.Status(fiber.StatusBadRequest)
return c.JSON(presenters.UserErrorResponse(errors.New(
"Full Name, Email, and Phone Number are required",
)))
}

result, err := user_service.Create(&requestBody)

if err != nil {
c.Status(fiber.StatusInternalServerError)
return c.JSON(presenters.UserErrorResponse(err))
}

return c.JSON(presenters.UserSuccessResponse(result))

}
}

func FindAllUsers(user_service services.IUserService) fiber.Handler {
return func(c *fiber.Ctx) error {
result, err := user_service.FindAll()

if err != nil {
c.Status(fiber.StatusInternalServerError)
return c.JSON(presenters.UserErrorResponse(err))
}

return c.JSON(presenters.UsersSuccessResponse(result))
}
}

func FindUserByID(user_service services.IUserService) fiber.Handler {
return func(c *fiber.Ctx) error {
idStr := c.Params("id")

id, err := strconv.ParseUint(idStr, 10, 32)

if err != nil {
c.Status(fiber.StatusBadRequest)
return c.JSON(presenters.UserErrorResponse(err))
}

result, err := user_service.FindByID(uint(id))

if err != nil {
c.Status(fiber.StatusInternalServerError)
return c.JSON(presenters.UserErrorResponse(err))
}

return c.JSON(presenters.UserSuccessResponse(result))
}
}
23 changes: 23 additions & 0 deletions api/handlers/user_referral.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package handlers

import (
"gss-backend/api/presenters"
services "gss-backend/pkg/services/user_referral"

"github.com/gofiber/fiber/v2"
)


func FindLeaderboardScores(userReferralService services.IUserReferralService) fiber.Handler {
return func(c *fiber.Ctx) error {
result, err := userReferralService.FindLeaderboardScores()

if err != nil {
c.Status(fiber.StatusInternalServerError)
return c.JSON(presenters.UserReferralErrorResponse(err))
}

return c.JSON(presenters.LeaderboardScoreSuceessResponse(result))
}
}

57 changes: 57 additions & 0 deletions api/presenters/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package presenters

import (
"gss-backend/pkg/models"

"github.com/gofiber/fiber/v2"
)

// UserPresenter is a struct that is going to be used to present the user data in a controlled way
// As a minor "business logic", I opted to not return the phone number and the email of the user
type UserPresenter struct {
ID uint `json:"id"`
FullName string `json:"full_name"`
ReferralCode string `json:"referral_code"`
}

// Functions to transform the user 'raw' data to presenters
func UserSuccessResponse(data *models.User) *fiber.Map {
user := UserPresenter{
ID: data.ID,
FullName: data.FullName,
ReferralCode: data.ReferralCode,

}

return &fiber.Map{
"status": "success",
"data": user,
"error": nil,
}
}

func UsersSuccessResponse(data *[]models.User) *fiber.Map {
var users []UserPresenter

for _, user := range *data {
users = append(users, UserPresenter{
ID: user.ID,
FullName: user.FullName,
ReferralCode: user.ReferralCode,
})
}

return &fiber.Map{
"status": "success",
"data": users,
"error": nil,
}
}

func UserErrorResponse(err error) *fiber.Map {
return &fiber.Map{
"status": "error",
"data": nil,
"error": err.Error(),
}
}
43 changes: 43 additions & 0 deletions api/presenters/user_referral.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package presenters

import (
repo "gss-backend/pkg/repositories/user_referral"

"github.com/gofiber/fiber/v2"
)

type PointsPresenter struct {
ID uint `json:"id"`
Points uint `json:"points"`
}

type LeaderboardScorePresenter struct {
ReferrerId uint `json:"referrer_id"`
FullName string `json:"full_name"`
ReferralsCount uint `json:"referrals_count"`
}

func LeaderboardScoreSuceessResponse(data *[]repo.LeaderboardScore) *fiber.Map {
var leaderboardScores []LeaderboardScorePresenter

for _, score := range *data {
leaderboardScores = append(leaderboardScores, LeaderboardScorePresenter{
ReferrerId: score.ReferrerId,
FullName: score.FullName,
ReferralsCount: score.ReferralsCount,
})
}
return &fiber.Map{
"status": "success",
"data": leaderboardScores,
"error": nil,
}
}

func UserReferralErrorResponse(err error) *fiber.Map {
return &fiber.Map{
"status": "error",
"data": nil,
"error": err.Error(),
}
}
15 changes: 15 additions & 0 deletions api/routes/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package routes

import (
"gss-backend/api/handlers"
services "gss-backend/pkg/services/user"

"github.com/gofiber/fiber/v2"
)

// UserRouter is a function created to define User related routes
func UserRouter(app fiber.Router, user_service services.IUserService) {
app.Post("/users", handlers.CreateUser(user_service))
app.Get("/users", handlers.FindAllUsers(user_service))
app.Get("/users/:id", handlers.FindUserByID(user_service))
}
13 changes: 13 additions & 0 deletions api/routes/user_referral.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package routes

import (
"gss-backend/api/handlers"
services "gss-backend/pkg/services/user_referral"

"github.com/gofiber/fiber/v2"
)


func UserReferralRouter(app fiber.Router, user_referral_service services.IUserReferralService) {
app.Get("/leaderboard", handlers.FindLeaderboardScores(user_referral_service))
}
91 changes: 91 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package main

import (
"fmt"
"gss-backend/api/routes"
"gss-backend/pkg/config"
"gss-backend/pkg/models"
emailService "gss-backend/pkg/services/email"
"log"

"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)

func main() {
// Loading configuration
log.Println("Loading configuration...")
config, err := config.NewConfig()

if err != nil {
log.Fatal(err)
}

log.Println("Configuration loaded!")

// Setting up Postgres DSN
dsn := fmt.Sprintf((
"host=%s port=%d user=%s password=%s dbname=%s sslmode=disable TimeZone=UTC"),
config.POSTGRES_HOST,
config.POSTGRES_PORT,
config.POSTGRES_USER,
config.POSTGRES_PASSWORD,
config.POSTGRES_DB,
)

// Connecting to the database
log.Println("Connecting to the database...")

db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})

if err != nil {
log.Fatal(err)
}

log.Println("Connected to the database!")

// Setting up Migrations
log.Println("Running migrations...")

err = db.AutoMigrate(&models.User{}, &models.UserReferral{})

if err != nil {
log.Fatal("Error running migrations", err)
}

log.Println("Migrations complete!")

// Setting up Fiber
app := fiber.New()
app.Use(cors.New())

app.Get("/", func(c *fiber.Ctx) error {
return c.SendString("GSS Gateway API is up and running! 🚀")
})

// Instatiating Repositories
repoContainer := NewRepositoryContainer(db)


// Instatiating Services
emailConfig := emailService.EmailConfig{
SMTPHost: config.SMTP_HOST,
SMTPPort: config.SMTP_PORT,
SMTPEmail: config.SMTP_EMAIL,
SMTPPassword: config.SMTP_PASSWORD,
}
serviceContainer := NewServiceContainer(repoContainer, emailConfig)

// Setting up routes
api := app.Group("/api")
routes.UserRouter(api, serviceContainer.UserService)
routes.UserReferralRouter(api, serviceContainer.UserReferralService)

// Starting the server
port := fmt.Sprintf(":%s", config.FIBER_PORT)
log.Fatal(app.Listen(port))


}
Loading