Skip to content

Commit 043f0df

Browse files
Merge pull request #57 from DanglingDynamo/master
2 parents c5a1d0d + 513faf6 commit 043f0df

File tree

5 files changed

+184
-1
lines changed

5 files changed

+184
-1
lines changed

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Define variables
22
DOCKER_COMPOSE = docker compose
3+
URI = "host= port= user= password= dbname=devsoc-24-backend"
4+
35
URI = "host=139.59.71.66 port=5430 user=admin password=d352F68MX2Hdh2pRFWoB3R9yg dbname=devsoc-24-backend"
46

57
# Targets
@@ -27,7 +29,7 @@ migrate-up:
2729
cd db/migrations && goose postgres $(URI) up && cd ../..
2830

2931
migrate-down:
30-
cd db/migrations && goose postgres $(URI) down-to 0 && cd ../..
32+
cd db/migrations && goose postgres $(URI) down && cd ../..
3133

3234
# Help target
3335
help:

internal/controllers/admin_user_controller.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,45 @@ func UnbanUser(ctx echo.Context) error {
172172
"status": "success",
173173
})
174174
}
175+
176+
func CheckIn(ctx echo.Context) error {
177+
var payload struct {
178+
Email string `json:"email" validate:"required"`
179+
}
180+
181+
if err := ctx.Bind(&payload); err != nil {
182+
return ctx.JSON(http.StatusBadRequest, map[string]string{
183+
"message": err.Error(),
184+
"status": "fail",
185+
})
186+
}
187+
188+
user, err := services.FindUserByEmail(payload.Email)
189+
if err != nil {
190+
if errors.Is(err, sql.ErrNoRows) {
191+
return ctx.JSON(http.StatusNotFound, map[string]string{
192+
"message": "User does not exist",
193+
"status": "fail",
194+
})
195+
}
196+
return ctx.JSON(http.StatusInternalServerError, map[string]string{
197+
"message": err.Error(),
198+
"status": "error",
199+
})
200+
}
201+
202+
user.IsAdded = true
203+
204+
err = services.UpdateUser(&user.User)
205+
if err != nil {
206+
return ctx.JSON(http.StatusInternalServerError, map[string]string{
207+
"message": err.Error(),
208+
"status": "fail",
209+
})
210+
}
211+
212+
return ctx.JSON(http.StatusOK, map[string]string{
213+
"message": "checkin successful",
214+
"status": "success",
215+
})
216+
}

internal/controllers/user_controller.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,3 +730,140 @@ func ResetPassword(ctx echo.Context) error {
730730
"message": "password reset successfully",
731731
})
732732
}
733+
734+
func AdminLogin(ctx echo.Context) error {
735+
var payload models.LoginRequest
736+
737+
if err := ctx.Bind(&payload); err != nil {
738+
return ctx.JSON(http.StatusBadRequest, map[string]string{
739+
"message": err.Error(),
740+
"status": "fail",
741+
})
742+
}
743+
744+
if err := ctx.Validate(&payload); err != nil {
745+
return ctx.JSON(http.StatusBadRequest, map[string]string{
746+
"message": err.Error(),
747+
"status": "fail",
748+
})
749+
}
750+
751+
payload.Email = strings.ToLower(payload.Email)
752+
753+
user, err := services.FindUserByEmail(payload.Email)
754+
if err != nil {
755+
if errors.Is(err, sql.ErrNoRows) {
756+
return ctx.JSON(http.StatusNotFound, map[string]string{
757+
"message": "user does not exist",
758+
"status": "fail",
759+
})
760+
}
761+
return ctx.JSON(http.StatusInternalServerError, map[string]string{
762+
"messsage": err.Error(),
763+
"status": "error",
764+
})
765+
}
766+
767+
if !user.IsVerified {
768+
return ctx.JSON(http.StatusForbidden, map[string]string{
769+
"message": "User is not verified",
770+
"status": "fail",
771+
})
772+
}
773+
774+
if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(payload.Password)); err != nil {
775+
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
776+
return ctx.JSON(http.StatusConflict, map[string]string{
777+
"message": "Invalid password",
778+
"status": "fail",
779+
})
780+
}
781+
return ctx.JSON(http.StatusInternalServerError, map[string]string{
782+
"message": err.Error(),
783+
"status": "error",
784+
})
785+
}
786+
787+
tokenVersionStr, err := database.RedisClient.Get(
788+
fmt.Sprintf("token_version:%s", user.User.Email))
789+
if err != nil && err != redis.Nil {
790+
return ctx.JSON(http.StatusInternalServerError, map[string]string{
791+
"status": "error",
792+
"message": err.Error(),
793+
})
794+
}
795+
796+
tokenVersion, _ := strconv.Atoi(tokenVersionStr)
797+
798+
accessToken, err := utils.CreateToken(utils.TokenPayload{
799+
Exp: time.Hour * 24,
800+
Email: user.User.Email,
801+
Role: user.Role,
802+
TokenVersion: tokenVersion + 1,
803+
}, utils.ACCESS_TOKEN)
804+
if err != nil {
805+
return ctx.JSON(http.StatusInternalServerError, map[string]string{
806+
"message": err.Error(),
807+
"status": "error",
808+
})
809+
}
810+
811+
refreshToken, err := utils.CreateToken(utils.TokenPayload{
812+
Exp: time.Hour * 24,
813+
Email: user.User.Email,
814+
}, utils.REFRESH_TOKEN)
815+
if err != nil {
816+
return ctx.JSON(http.StatusInternalServerError, map[string]string{
817+
"message": err.Error(),
818+
"status": "error",
819+
})
820+
}
821+
822+
if err := database.RedisClient.Set(fmt.Sprintf("token_version:%s", user.User.Email),
823+
fmt.Sprint(tokenVersion+1), time.Hour*1); err != nil {
824+
return ctx.JSON(http.StatusInternalServerError, map[string]string{
825+
"status": "error",
826+
"message": err.Error(),
827+
})
828+
}
829+
830+
if err := database.RedisClient.Set(user.User.Email, refreshToken, time.Hour*1); err != nil {
831+
return ctx.JSON(http.StatusInternalServerError, map[string]string{
832+
"status": "error",
833+
"message": err.Error(),
834+
})
835+
}
836+
837+
ctx.SetCookie(&http.Cookie{
838+
Name: os.Getenv("ACCESS_COOKIE_NAME"),
839+
Value: accessToken,
840+
HttpOnly: true,
841+
SameSite: http.SameSiteStrictMode,
842+
MaxAge: 86400,
843+
Secure: true,
844+
})
845+
846+
ctx.SetCookie(&http.Cookie{
847+
Name: os.Getenv("REFRESH_COOKIE_NAME"),
848+
Value: refreshToken,
849+
HttpOnly: true,
850+
SameSite: http.SameSiteStrictMode,
851+
MaxAge: 86400,
852+
Secure: true,
853+
})
854+
855+
if !user.IsProfileComplete {
856+
return ctx.JSON(http.StatusLocked, map[string]interface{}{
857+
"message": "profile not completed",
858+
"status": "fail",
859+
"data": map[string]interface{}{
860+
"profile_complete": user.IsProfileComplete,
861+
},
862+
})
863+
}
864+
865+
return ctx.JSON(http.StatusOK, map[string]interface{}{
866+
"message": "login successful",
867+
"status": "success",
868+
})
869+
}

internal/routes/admin_routes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ func AdminRoutes(incomingRoutes *echo.Echo) {
1515
admin.GET("/user/:email", controllers.GetUserByEmail)
1616
admin.POST("/user/ban", controllers.BanUser, middleware.EditOnly)
1717
admin.POST("/user/unban", controllers.UnbanUser, middleware.EditOnly)
18+
admin.POST("/user/checkin", controllers.CheckIn, middleware.EditOnly)
1819
admin.GET("/vitians", controllers.GetAllVitians)
1920
admin.GET("/females", controllers.GetAllFemales)
2021

internal/routes/user_routes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ func UserRoutes(incomingRoutes *echo.Echo) {
1313
incomingRoutes.POST("/resend", controllers.ResendOTP)
1414
incomingRoutes.POST("/reset-password", controllers.RequestResetPassword)
1515
incomingRoutes.PATCH("/reset-password", controllers.ResetPassword)
16+
incomingRoutes.POST("/admin/login", controllers.AdminLogin)
1617

1718
user := incomingRoutes.Group("/user")
1819
user.POST(

0 commit comments

Comments
 (0)