first commit
This commit is contained in:
71
services/auth/auth.routes.go
Normal file
71
services/auth/auth.routes.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
// Login login using westone account
|
||||
//
|
||||
// @Summary login using westone account
|
||||
// @Description login using westone account
|
||||
// @Tags Auth
|
||||
// @Accept json
|
||||
// @Param parameter body types.SignInPayload true "parameter"
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]any
|
||||
// @Failure 400 {object} map[string]any
|
||||
// @Failure 500 {object} map[string]any
|
||||
// @Router /westone/api/v1/auth/login [post]
|
||||
func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
var payload types.SignInPayload
|
||||
fmt.Println("User Agent")
|
||||
fmt.Println(r.UserAgent())
|
||||
if err := utils.ParseJSON(r, &payload); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, err)
|
||||
h.store.LogSignIn(payload.Email, r.RemoteAddr, "FAILED", "LOGIN", "westone")
|
||||
return
|
||||
}
|
||||
|
||||
if err := utils.Validate.Struct(payload); err != nil {
|
||||
errorz := err.(validator.ValidationErrors)
|
||||
utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("invalid payload: %v", errorz))
|
||||
h.store.LogSignIn(payload.Email, r.RemoteAddr, "FAILED", "LOGIN", "westone")
|
||||
return
|
||||
}
|
||||
|
||||
hashedPassword := HashWithMD5(payload.Password)
|
||||
response, err := h.store.SignInWestone(payload.Email, hashedPassword)
|
||||
if err != nil {
|
||||
var logError *utils.LogError
|
||||
if errors.As(err, &logError) {
|
||||
h.errorz.CreateErrorLog(*logError)
|
||||
utils.WriteErrorLog(w, http.StatusBadRequest, *logError)
|
||||
}
|
||||
h.store.LogSignIn(payload.Email, r.RemoteAddr, "FAILED", "LOGIN", "westone")
|
||||
return
|
||||
}
|
||||
|
||||
timenow := time.Now()
|
||||
token, err := CreateJWT(types.DataJWT{
|
||||
User: *response,
|
||||
Ip: r.RemoteAddr,
|
||||
Agent: r.UserAgent(),
|
||||
Version: "v1",
|
||||
LastLogin: timenow.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
h.store.LogSignIn(payload.Email, r.RemoteAddr, "FAILED", "LOGIN", "westone")
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSONLogin(w, http.StatusOK, response, token, "westone")
|
||||
}
|
||||
77
services/auth/auth.store.go
Normal file
77
services/auth/auth.store.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
func (s *Store) SignInWestone(email string, password string) (*types.User, error) {
|
||||
user := new(types.User)
|
||||
|
||||
qry := `
|
||||
SELECT
|
||||
M_UserID,
|
||||
M_UserName,
|
||||
M_UserGroupDashboard,
|
||||
IFNULL(M_UserFullName, "") as M_StaffName,
|
||||
10000000 as time_autologout
|
||||
FROM m_user
|
||||
JOIN m_usergroup ON M_UserM_UserGroupID = M_UserGroupID
|
||||
LEFT JOIN m_staff on M_UserM_StaffID = M_StaffID
|
||||
WHERE M_UserName = ? AND M_UserPassword = ? AND M_UserIsActive = 'Y'
|
||||
`
|
||||
if err := s.db.Get(user, qry, email, password); err != nil {
|
||||
return nil, &utils.LogError{
|
||||
Code: "151",
|
||||
TraceID: utils.RandomTraceID(15),
|
||||
Type: "AUTH",
|
||||
Params: "email: " + email,
|
||||
Query: "-",
|
||||
Message: err.Error(),
|
||||
TimeStamp: time.Now().Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
}
|
||||
user.Type_Akun = "westone"
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *Store) LogSignIn(email string, ip string, status string, tipe string, provider string) error {
|
||||
tx, err := s.db.BeginTxx(context.Background(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
logval := types.LogLogin{
|
||||
LogLoginMUserEmail: email,
|
||||
LogLoginDateTime: time.Now().Format("2006-01-02 15:04:05"),
|
||||
LogLoginIP: ip,
|
||||
LogLoginType: tipe,
|
||||
LogLoginStatus: status,
|
||||
LogLoginLogin: provider,
|
||||
}
|
||||
|
||||
qry := `INSERT INTO log_login
|
||||
(Log_LoginM_UserEmail, Log_LoginDateTime, Log_LoginIP, Log_LoginType, Log_LoginStatus, Log_LoginLogin)
|
||||
VALUES (:Log_LoginM_UserEmail, :Log_LoginDateTime, :Log_LoginIP, :Log_LoginType, :Log_LoginStatus, :Log_LoginLogin)`
|
||||
|
||||
_, err = tx.NamedExec(qry, logval)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
134
services/auth/jwt.go
Normal file
134
services/auth/jwt.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"sismedika.com/sas/westone/configs"
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
type ContextKey string
|
||||
|
||||
const UserContextKey ContextKey = "mk1S2sKM12KASd02dp1"
|
||||
|
||||
// func WithJWTAuth(handlerFunc http.HandlerFunc, store types.UserStore) http.HandlerFunc {
|
||||
// return func(w http.ResponseWriter, r *http.Request) {
|
||||
// tokenString := utils.GetTokenFromRequest(r)
|
||||
// token, err := validateJWT(tokenString)
|
||||
// if err != nil {
|
||||
// log.Printf("failed to validate token: %v", err)
|
||||
// permissionDenied(w)
|
||||
// return
|
||||
// }
|
||||
|
||||
// if !token.Valid {
|
||||
// log.Println("invalid token")
|
||||
// permissionDenied(w)
|
||||
// return
|
||||
// }
|
||||
|
||||
// claims := token.Claims.(jwt.MapClaims)
|
||||
// str := claims["userID"].(string)
|
||||
|
||||
// userID, err := strconv.Atoi(str)
|
||||
// if err != nil {
|
||||
// log.Printf("failed to convert userID to int: %v", err)
|
||||
// permissionDenied(w)
|
||||
// return
|
||||
// }
|
||||
|
||||
// u, err := store.GetUserByID(userID)
|
||||
// if err != nil {
|
||||
// log.Printf("failed to get user by id: %v", err)
|
||||
// permissionDenied(w)
|
||||
// return
|
||||
// }
|
||||
|
||||
// // Add the user to the context
|
||||
// ctx := r.Context()
|
||||
// ctx = context.WithValue(ctx, UserKey, u.MUserID)
|
||||
// r = r.WithContext(ctx)
|
||||
|
||||
// // Call the function if the token is valid
|
||||
// handlerFunc(w, r)
|
||||
// }
|
||||
// }
|
||||
|
||||
func CreateJWT(data types.DataJWT) (string, error) {
|
||||
secret := []byte(configs.Envs.JWTSecret)
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"M_UserID": data.M_UserID,
|
||||
"M_UserEmail": data.M_UserEmail,
|
||||
"M_UserUsername": data.M_UserUsername,
|
||||
"M_UserGroupDashboard": data.M_UserGroupDashboard,
|
||||
"M_UserDefaultTSampleStationID": data.M_UserDefaultTSampleStationID,
|
||||
"M_StaffName": data.M_StaffName,
|
||||
"Is_Courier": data.Is_Courier,
|
||||
"Time_Autologout": data.Time_Autologout,
|
||||
"Type_Akun": data.Type_Akun,
|
||||
"IP": data.Ip,
|
||||
"Agent": data.Agent,
|
||||
"Version": data.Version,
|
||||
"LastLogin": data.LastLogin,
|
||||
})
|
||||
|
||||
tokenString, err := token.SignedString(secret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tokenString, err
|
||||
}
|
||||
|
||||
func validateJWT(tokenString string) (*jwt.Token, error) {
|
||||
return jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
|
||||
return []byte(configs.Envs.JWTSecret), nil
|
||||
})
|
||||
}
|
||||
|
||||
func permissionDenied(w http.ResponseWriter) {
|
||||
utils.WriteError(w, http.StatusForbidden, fmt.Errorf("PERMISSION DENIED"))
|
||||
}
|
||||
|
||||
func GetUserIDFromContext(ctx context.Context) int {
|
||||
userID, ok := ctx.Value(UserContextKey).(int)
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
return userID
|
||||
}
|
||||
|
||||
func AuthMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tokenstr := utils.GetTokenFromRequest(r)
|
||||
token, err := validateJWT(tokenstr)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed to validate jwt token: %v", err)
|
||||
permissionDenied(w)
|
||||
return
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
log.Println("[ERROR] Invalid token")
|
||||
permissionDenied(w)
|
||||
return
|
||||
}
|
||||
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(ctx, UserContextKey, claims)
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
18
services/auth/jwt_test.go
Normal file
18
services/auth/jwt_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"sismedika.com/sas/westone/types"
|
||||
)
|
||||
|
||||
func TestCreateJWT(t *testing.T) {
|
||||
token, err := CreateJWT(types.DataJWT{})
|
||||
if err != nil {
|
||||
t.Errorf("error creating JWT: %v", err)
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
t.Error("expected token to be not empty")
|
||||
}
|
||||
}
|
||||
73
services/auth/oauth.go
Normal file
73
services/auth/oauth.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/markbates/goth"
|
||||
"github.com/markbates/goth/gothic"
|
||||
"github.com/markbates/goth/providers/google"
|
||||
"sismedika.com/sas/westone/configs"
|
||||
)
|
||||
|
||||
var maxAge = 86400 * 1 // 30 days
|
||||
var isProd = false // set to true if in production
|
||||
var key = configs.Envs.SessionKey
|
||||
var store = sessions.NewCookieStore([]byte(key))
|
||||
var usersession = "user"
|
||||
|
||||
func NewOAuth() {
|
||||
googleClientID := configs.Envs.GoogleClientID
|
||||
googleClientSecret := configs.Envs.GoogleClientSecret
|
||||
callback := configs.Envs.PublicHost + ":" + configs.Envs.Port + "/api/v1/auth/google/callback"
|
||||
|
||||
store.MaxAge(maxAge)
|
||||
store.Options.Path = "/"
|
||||
store.Options.HttpOnly = true
|
||||
store.Options.Secure = isProd
|
||||
store.Options.SameSite = http.SameSiteLaxMode
|
||||
|
||||
gothic.Store = store
|
||||
|
||||
goth.UseProviders(
|
||||
google.New(googleClientID, googleClientSecret, callback),
|
||||
)
|
||||
}
|
||||
|
||||
func addUsertoSession(w http.ResponseWriter, r *http.Request, user goth.User) error {
|
||||
user_session, err := store.Get(r, usersession)
|
||||
if err != nil {
|
||||
user_session, err = store.New(r, usersession)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] get user to session, error: ", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
user.RawData = map[string]interface{}{}
|
||||
user_session.Values["user"] = user
|
||||
err = user_session.Save(r, w)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] saving user to session, error: ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeUsertoSession(w http.ResponseWriter, r *http.Request) error {
|
||||
user_session, err := store.Get(r, usersession)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] get user to session, error: ", err)
|
||||
return err
|
||||
}
|
||||
user_session.Values["user"] = goth.User{}
|
||||
err = user_session.Save(r, w)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] remove user to session, error: ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
249
services/auth/oauth.routes.go
Normal file
249
services/auth/oauth.routes.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/markbates/goth"
|
||||
"github.com/markbates/goth/gothic"
|
||||
"sismedika.com/sas/westone/configs"
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
store types.OauthStore
|
||||
errorz types.ErrorLogStore
|
||||
usrStore types.UserStore
|
||||
}
|
||||
|
||||
func NewHandler(store types.OauthStore, errorz types.ErrorLogStore, usrStore types.UserStore) *Handler {
|
||||
return &Handler{
|
||||
store: store,
|
||||
errorz: errorz,
|
||||
usrStore: usrStore,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) RegisterRoutes(router *mux.Router) {
|
||||
authroute := router.PathPrefix("/auth").Subrouter()
|
||||
|
||||
authroute.HandleFunc("/generateauthcode", h.handlerGenerateAuthCode).Methods(http.MethodPost)
|
||||
|
||||
// auth
|
||||
authroute.HandleFunc("/login", h.handleLogin).Methods(http.MethodPost)
|
||||
// oauth
|
||||
authroute.HandleFunc("/{provider}/login", h.handleAuthLogin).Methods(http.MethodGet)
|
||||
authroute.HandleFunc("/{provider}/logout", h.handleAuthLogout).Methods(http.MethodGet)
|
||||
authroute.HandleFunc("/{provider}/callback", h.handleAuthCallback).Methods(http.MethodGet)
|
||||
authroute.HandleFunc("/{provider}/linking", h.handleAuthLinkAccount).Methods(http.MethodPost)
|
||||
authroute.HandleFunc("/{provider}/authcode", h.handlerRedirectCode).Methods(http.MethodGet)
|
||||
|
||||
protec := authroute.PathPrefix("/redirect").Subrouter()
|
||||
protec.Use(AuthMiddleware)
|
||||
protec.HandleFunc("/dashboard", h.handleRedirectDash).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
func (h *Handler) handleAuthLogin(w http.ResponseWriter, r *http.Request) {
|
||||
if gothUser, err := gothic.CompleteUserAuth(w, r); err == nil {
|
||||
// log.Println(gothUser)
|
||||
log.Printf("[INFO] account found: %v", gothUser.Email)
|
||||
} else {
|
||||
gothic.BeginAuthHandler(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) handleAuthCallback(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
provider := vars["provider"]
|
||||
|
||||
akungoogle, err := gothic.CompleteUserAuth(w, r)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
// add user to cookie session
|
||||
err = addUsertoSession(w, r, akungoogle)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
// check google is linked to server
|
||||
userID, err := h.store.CheckGoogleAccountLinked(types.UserGoogle{M_UserGoogleEmail: akungoogle.Email, M_UserGoogleIdentifier: akungoogle.UserID})
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
if userID > 0 {
|
||||
// generate jwt token
|
||||
user, err := h.usrStore.GetUserByID(userID)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
user.Type_Akun = provider
|
||||
timenow := time.Now()
|
||||
token, err := CreateJWT(types.DataJWT{
|
||||
User: *user,
|
||||
Ip: r.RemoteAddr,
|
||||
Agent: r.UserAgent(),
|
||||
Version: "v1",
|
||||
LastLogin: timenow.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
// data := "Bearer " + token
|
||||
// w.Header().Set("Authorization", data)
|
||||
// http.Redirect(w, r, "/api/v1/auth/redirect/dashboard", http.StatusFound)
|
||||
utils.WriteJSONLogin(w, http.StatusOK, user, token, provider)
|
||||
} else {
|
||||
log.Println("[INFO] redirect to auth-code")
|
||||
// http.Redirect(w, r, "/api/v1/auth/"+provider+"/authcode", http.StatusFound)
|
||||
http.Redirect(w, r, configs.Envs.PublicHost+"/auth-code", http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) handleRedirectDash(w http.ResponseWriter, r *http.Request) {
|
||||
claims, ok := r.Context().Value(UserContextKey).(jwt.MapClaims)
|
||||
if !ok {
|
||||
utils.WriteError(w, http.StatusInternalServerError, fmt.Errorf("token not found in context"))
|
||||
return
|
||||
}
|
||||
|
||||
username := claims["Username"].(string)
|
||||
temp := "WELCOME TO DASHBOARD, " + username
|
||||
utils.WriteJSON(w, http.StatusOK, temp)
|
||||
}
|
||||
|
||||
func (h *Handler) handlerRedirectCode(w http.ResponseWriter, r *http.Request) {
|
||||
user_session, _ := store.Get(r, usersession)
|
||||
user := user_session.Values["user"].(goth.User)
|
||||
if user.IDToken == "" {
|
||||
log.Println("[ERROR] token not found in context")
|
||||
utils.WriteJSON(w, http.StatusInternalServerError, "not found")
|
||||
return
|
||||
}
|
||||
|
||||
temp := "INSERT AUTH CODE, " + user.Email
|
||||
utils.WriteJSON(w, http.StatusOK, temp)
|
||||
}
|
||||
|
||||
func (h *Handler) handleAuthLogout(w http.ResponseWriter, r *http.Request) {
|
||||
err := gothic.Logout(w, r)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
err = removeUsertoSession(w, r)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, "success logout")
|
||||
}
|
||||
|
||||
func (h *Handler) handlerGenerateAuthCode(w http.ResponseWriter, r *http.Request) {
|
||||
var payload types.GenerateAuthCode
|
||||
if err := utils.ParseJSON(r, &payload); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := utils.Validate.Struct(payload); err != nil {
|
||||
errorss := err.(validator.ValidationErrors)
|
||||
utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("invalid payload: %v", errorss))
|
||||
return
|
||||
}
|
||||
|
||||
err := h.store.GenerateAuthCode(payload.Email, payload.Types, payload.UserID)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] failed generate auth code")
|
||||
utils.WriteJSON(w, http.StatusInternalServerError, "failed generate auth code")
|
||||
return
|
||||
}
|
||||
|
||||
msg := "Auth Code Generated for " + payload.Email
|
||||
utils.WriteJSON(w, http.StatusOK, msg)
|
||||
}
|
||||
|
||||
func (h *Handler) handleAuthLinkAccount(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
provider := vars["provider"]
|
||||
|
||||
// var payload types.AuthCode
|
||||
var payload types.AuthCodePayload
|
||||
if err := utils.ParseJSON(r, &payload); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := utils.Validate.Struct(payload); err != nil {
|
||||
errors := err.(validator.ValidationErrors)
|
||||
utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("invalid payload: %v", errors))
|
||||
return
|
||||
}
|
||||
|
||||
user_session, _ := store.Get(r, usersession)
|
||||
akunGoogle := user_session.Values["user"].(goth.User)
|
||||
if akunGoogle.IDToken == "" {
|
||||
log.Println("[ERROR] google token not found in context")
|
||||
utils.WriteError(w, http.StatusInternalServerError, fmt.Errorf("token not found"))
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := h.store.CompareAuthCode(payload.Code, akunGoogle, provider)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] compare auth code")
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.usrStore.GetUserByID(userID)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] cant get m_user data")
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
user.Type_Akun = provider
|
||||
timenow := time.Now()
|
||||
token, err := CreateJWT(types.DataJWT{
|
||||
User: *user,
|
||||
Ip: r.RemoteAddr,
|
||||
Agent: r.UserAgent(),
|
||||
Version: "v1",
|
||||
LastLogin: timenow.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = h.store.AddGoolgeAccount(types.UserGoogle{
|
||||
M_UserGoogleM_UserID: user.M_UserID,
|
||||
M_UserGoogleEmail: akunGoogle.Email,
|
||||
M_UserGoogleIdentifier: akunGoogle.UserID,
|
||||
M_UserGoogleCode: payload.Code,
|
||||
M_UserGoogleToken: token,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] failed insert google account to db, %v", err)
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSONLogin(w, http.StatusOK, user, token, provider)
|
||||
}
|
||||
143
services/auth/oauth.store.go
Normal file
143
services/auth/oauth.store.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/markbates/goth"
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewStore(db *sqlx.DB) *Store {
|
||||
return &Store{db: db}
|
||||
}
|
||||
|
||||
func (s *Store) AddGoolgeAccount(user types.UserGoogle) error {
|
||||
tx, err := s.db.BeginTxx(context.Background(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
query := `INSERT INTO m_usergoogle (
|
||||
M_UserGoogleM_UserID,
|
||||
M_UserGoogleEmail,
|
||||
M_UserGoogleIdentifier,
|
||||
M_UserGoogleCode,
|
||||
M_UserGoogleToken
|
||||
) VALUES (:M_UserGoogleM_UserID, :M_UserGoogleEmail, :M_UserGoogleIdentifier, :M_UserGoogleCode, :M_UserGoogleToken)`
|
||||
_, err = tx.NamedExec(query, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) CheckGoogleAccountLinked(user types.UserGoogle) (int, error) {
|
||||
var userID int
|
||||
|
||||
qry := `SELECT
|
||||
M_UserGoogleM_UserID
|
||||
FROM m_usergoogle
|
||||
WHERE M_UserGoogleEmail = ? AND M_UserGoogleIdentifier = ?`
|
||||
if err := s.db.Get(&userID, qry, user.M_UserGoogleEmail, user.M_UserGoogleIdentifier); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if userID < 1 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return userID, nil
|
||||
}
|
||||
|
||||
func (s *Store) GenerateAuthCode(email string, jenis string, userid int) error {
|
||||
payload := types.AuthCode{
|
||||
AuthCodeMUserID: userid,
|
||||
AuthCodeUser: email,
|
||||
AuthCodeType: jenis,
|
||||
AuthCodeCode: utils.RandomTraceID(6),
|
||||
}
|
||||
|
||||
tx, err := s.db.BeginTxx(context.Background(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
sql := `INSERT INTO x_auth_code (
|
||||
AuthCodeMUserID,
|
||||
AuthCodeUser,
|
||||
AuthCodeType,
|
||||
AuthCodeCode
|
||||
) VALUES (:AuthCodeMUserID, :AuthCodeUser, :AuthCodeType, :AuthCodeCode)`
|
||||
_, err = tx.NamedExec(sql, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) CompareAuthCode(authcode string, user goth.User, typez string) (int, error) {
|
||||
var code types.AuthCode
|
||||
|
||||
qry := `SELECT
|
||||
AuthCodeID,
|
||||
AuthCodeMUserID,
|
||||
AuthCodeUser,
|
||||
AuthCodeType,
|
||||
AuthCodeCode,
|
||||
AuthCodeIsUsed
|
||||
FROM x_auth_code
|
||||
WHERE AuthCodeUser = ? AND AuthCodeType = ?
|
||||
AND AuthCodeIsUsed = 'N' AND AuthCodeIsActive = 'Y'`
|
||||
if err := s.db.Get(&code, qry, user.Email, typez); err != nil {
|
||||
return 0, fmt.Errorf("auth code not found, %v", err)
|
||||
}
|
||||
|
||||
if authcode != code.AuthCodeCode {
|
||||
return 0, fmt.Errorf("auth code do not match")
|
||||
} else {
|
||||
inst := `
|
||||
UPDATE x_auth_code
|
||||
SET AuthCodeIsUsed = 'Y'
|
||||
WHERE AuthCodeUser = ? AND AuthCodeType = ? AND AuthCodeCode = ?
|
||||
`
|
||||
if _, err := s.db.Exec(inst, user.Email, typez, authcode); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return code.AuthCodeMUserID, nil
|
||||
}
|
||||
}
|
||||
29
services/auth/password.go
Normal file
29
services/auth/password.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func HashPassword(password string) (string, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(hash), nil
|
||||
}
|
||||
|
||||
func ComparePasswords(hashed string, plain []byte) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashed), plain)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func HashWithMD5(password string) string {
|
||||
salt := "545"
|
||||
data := []byte(salt + password + salt)
|
||||
hash := md5.Sum(data)
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
34
services/auth/password_test.go
Normal file
34
services/auth/password_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHashPassword(t *testing.T) {
|
||||
hash, err := HashPassword("password")
|
||||
if err != nil {
|
||||
t.Errorf("error hashing password: %v", err)
|
||||
}
|
||||
|
||||
if hash == "" {
|
||||
t.Error("expected hash to be not empty")
|
||||
}
|
||||
|
||||
if hash == "password" {
|
||||
t.Error("expected hash to be different from password")
|
||||
}
|
||||
}
|
||||
|
||||
func TestComparePasswords(t *testing.T) {
|
||||
hash, err := HashPassword("password")
|
||||
if err != nil {
|
||||
t.Errorf("error hashing password: %v", err)
|
||||
}
|
||||
|
||||
if !ComparePasswords(hash, []byte("password")) {
|
||||
t.Errorf("expected password to match hash")
|
||||
}
|
||||
if ComparePasswords(hash, []byte("notpassword")) {
|
||||
t.Errorf("expected password to not match hash")
|
||||
}
|
||||
}
|
||||
77
services/cart/routes.go
Normal file
77
services/cart/routes.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package cart
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gorilla/mux"
|
||||
"sismedika.com/sas/westone/services/auth"
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
store types.ProductStore
|
||||
orderStore types.OrderStore
|
||||
userStore types.UserStore
|
||||
}
|
||||
|
||||
func NewHandler(
|
||||
store types.ProductStore,
|
||||
orderStore types.OrderStore,
|
||||
userStore types.UserStore,
|
||||
) *Handler {
|
||||
return &Handler{
|
||||
store: store,
|
||||
orderStore: orderStore,
|
||||
userStore: userStore,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) RegisterRoutes(router *mux.Router) {
|
||||
cart := router.PathPrefix("/cart").Subrouter()
|
||||
cart.Use(auth.AuthMiddleware)
|
||||
cart.HandleFunc("/checkout", h.handleCheckout).Methods(http.MethodPost)
|
||||
// router.HandleFunc("/cart/checkout", auth.WithJWTAuth(h.handleCheckout)).Methods(http.MethodPost)
|
||||
}
|
||||
|
||||
func (h *Handler) handleCheckout(w http.ResponseWriter, r *http.Request) {
|
||||
userID := auth.GetUserIDFromContext(r.Context())
|
||||
|
||||
var cart types.CartCheckoutPayload
|
||||
if err := utils.ParseJSON(r, &cart); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := utils.Validate.Struct(cart); err != nil {
|
||||
errors := err.(validator.ValidationErrors)
|
||||
utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("invalid payload: %v", errors))
|
||||
return
|
||||
}
|
||||
|
||||
productIds, err := getCartItemsIDs(cart.Items)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
// get products
|
||||
products, err := h.store.GetProductsByID(productIds)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
orderID, totalPrice, err := h.createOrder(products, cart.Items, userID)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"total_price": totalPrice,
|
||||
"order_id": orderID,
|
||||
})
|
||||
}
|
||||
214
services/cart/routes_test.go
Normal file
214
services/cart/routes_test.go
Normal file
@@ -0,0 +1,214 @@
|
||||
package cart
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"sismedika.com/sas/westone/types"
|
||||
)
|
||||
|
||||
var mockProducts = []types.Product{
|
||||
{ID: 1, Name: "product 1", Price: 10, Quantity: 100},
|
||||
{ID: 2, Name: "product 2", Price: 20, Quantity: 200},
|
||||
{ID: 3, Name: "product 3", Price: 30, Quantity: 300},
|
||||
{ID: 4, Name: "empty stock", Price: 30, Quantity: 0},
|
||||
{ID: 5, Name: "almost stock", Price: 30, Quantity: 1},
|
||||
}
|
||||
|
||||
func TestCartServiceHandler(t *testing.T) {
|
||||
productStore := &mockProductStore{}
|
||||
orderStore := &mockOrderStore{}
|
||||
handler := NewHandler(productStore, orderStore, nil)
|
||||
|
||||
t.Run("should fail to checkout if the cart items do not exist", func(t *testing.T) {
|
||||
payload := types.CartCheckoutPayload{
|
||||
Items: []types.CartCheckoutItem{
|
||||
{ProductID: 99, Quantity: 100},
|
||||
},
|
||||
}
|
||||
|
||||
marshalled, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, "/cart/checkout", bytes.NewBuffer(marshalled))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/cart/checkout", handler.handleCheckout).Methods(http.MethodPost)
|
||||
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status code %d, got %d", http.StatusBadRequest, rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should fail to checkout if the cart has negative quantities", func(t *testing.T) {
|
||||
payload := types.CartCheckoutPayload{
|
||||
Items: []types.CartCheckoutItem{
|
||||
{ProductID: 1, Quantity: 0}, // invalid quantity
|
||||
},
|
||||
}
|
||||
|
||||
marshalled, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, "/cart/checkout", bytes.NewBuffer(marshalled))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/cart/checkout", handler.handleCheckout).Methods(http.MethodPost)
|
||||
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status code %d, got %d", http.StatusBadRequest, rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should fail to checkout if there is no stock for an item", func(t *testing.T) {
|
||||
payload := types.CartCheckoutPayload{
|
||||
Items: []types.CartCheckoutItem{
|
||||
{ProductID: 4, Quantity: 2},
|
||||
},
|
||||
}
|
||||
|
||||
marshalled, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, "/cart/checkout", bytes.NewBuffer(marshalled))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/cart/checkout", handler.handleCheckout).Methods(http.MethodPost)
|
||||
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status code %d, got %d", http.StatusBadRequest, rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should fail to checkout if there is not enough stock", func(t *testing.T) {
|
||||
payload := types.CartCheckoutPayload{
|
||||
Items: []types.CartCheckoutItem{
|
||||
{ProductID: 5, Quantity: 2},
|
||||
},
|
||||
}
|
||||
|
||||
marshalled, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, "/cart/checkout", bytes.NewBuffer(marshalled))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/cart/checkout", handler.handleCheckout).Methods(http.MethodPost)
|
||||
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status code %d, got %d", http.StatusBadRequest, rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should checkout and calculate the price correctly", func(t *testing.T) {
|
||||
payload := types.CartCheckoutPayload{
|
||||
Items: []types.CartCheckoutItem{
|
||||
{ProductID: 1, Quantity: 10},
|
||||
{ProductID: 2, Quantity: 20},
|
||||
{ProductID: 5, Quantity: 1},
|
||||
},
|
||||
}
|
||||
|
||||
marshalled, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, "/cart/checkout", bytes.NewBuffer(marshalled))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/cart/checkout", handler.handleCheckout).Methods(http.MethodPost)
|
||||
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Errorf("expected status code %d, got %d", http.StatusOK, rr.Code)
|
||||
}
|
||||
|
||||
var response map[string]interface{}
|
||||
if err := json.NewDecoder(rr.Body).Decode(&response); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if response["total_price"] != 530.0 {
|
||||
t.Errorf("expected total price to be 530, got %f", response["total_price"])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type mockProductStore struct{}
|
||||
|
||||
func (m *mockProductStore) GetProductByID(productID int) (*types.Product, error) {
|
||||
return &types.Product{}, nil
|
||||
}
|
||||
|
||||
func (m *mockProductStore) GetProducts() ([]*types.Product, error) {
|
||||
return []*types.Product{}, nil
|
||||
}
|
||||
|
||||
func (m *mockProductStore) CreateProduct(product types.CreateProductPayload) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockProductStore) GetProductsByID(ids []int) ([]types.Product, error) {
|
||||
return mockProducts, nil
|
||||
}
|
||||
|
||||
func (m *mockProductStore) UpdateProduct(product types.Product) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type mockOrderStore struct{}
|
||||
|
||||
func (m *mockOrderStore) CreateOrder(order types.Order) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func (m *mockOrderStore) CreateOrderItem(orderItem types.OrderItem) error {
|
||||
return nil
|
||||
}
|
||||
96
services/cart/service.go
Normal file
96
services/cart/service.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package cart
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sismedika.com/sas/westone/types"
|
||||
)
|
||||
|
||||
func getCartItemsIDs(items []types.CartCheckoutItem) ([]int, error) {
|
||||
productIds := make([]int, len(items))
|
||||
for i, item := range items {
|
||||
if item.Quantity <= 0 {
|
||||
return nil, fmt.Errorf("invalid quantity for product %d", item.ProductID)
|
||||
}
|
||||
|
||||
productIds[i] = item.ProductID
|
||||
}
|
||||
|
||||
return productIds, nil
|
||||
}
|
||||
|
||||
func checkIfCartIsInStock(cartItems []types.CartCheckoutItem, products map[int]types.Product) error {
|
||||
if len(cartItems) == 0 {
|
||||
return fmt.Errorf("cart is empty")
|
||||
}
|
||||
|
||||
for _, item := range cartItems {
|
||||
product, ok := products[item.ProductID]
|
||||
if !ok {
|
||||
return fmt.Errorf("product %d is not available in the store, please refresh your cart", item.ProductID)
|
||||
}
|
||||
|
||||
if product.Quantity < item.Quantity {
|
||||
return fmt.Errorf("product %s is not available in the quantity requested", product.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func calculateTotalPrice(cartItems []types.CartCheckoutItem, products map[int]types.Product) float64 {
|
||||
var total float64
|
||||
|
||||
for _, item := range cartItems {
|
||||
product := products[item.ProductID]
|
||||
total += product.Price * float64(item.Quantity)
|
||||
}
|
||||
|
||||
return total
|
||||
}
|
||||
|
||||
func (h *Handler) createOrder(products []types.Product, cartItems []types.CartCheckoutItem, userID int) (int, float64, error) {
|
||||
// create a map of products for easier access
|
||||
productsMap := make(map[int]types.Product)
|
||||
for _, product := range products {
|
||||
productsMap[product.ID] = product
|
||||
}
|
||||
|
||||
// check if all products are available
|
||||
if err := checkIfCartIsInStock(cartItems, productsMap); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
// calculate total price
|
||||
totalPrice := calculateTotalPrice(cartItems, productsMap)
|
||||
|
||||
// reduce the quantity of products in the store
|
||||
for _, item := range cartItems {
|
||||
product := productsMap[item.ProductID]
|
||||
product.Quantity -= item.Quantity
|
||||
h.store.UpdateProduct(product)
|
||||
}
|
||||
|
||||
// create order record
|
||||
orderID, err := h.orderStore.CreateOrder(types.Order{
|
||||
UserID: userID,
|
||||
Total: totalPrice,
|
||||
Status: "pending",
|
||||
Address: "some address", // could fetch address from a user addresses table
|
||||
})
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
// create order the items records
|
||||
for _, item := range cartItems {
|
||||
h.orderStore.CreateOrderItem(types.OrderItem{
|
||||
OrderID: orderID,
|
||||
ProductID: item.ProductID,
|
||||
Quantity: item.Quantity,
|
||||
Price: productsMap[item.ProductID].Price,
|
||||
})
|
||||
}
|
||||
|
||||
return orderID, totalPrice, nil
|
||||
}
|
||||
62
services/doctor/doctor.routes.go
Normal file
62
services/doctor/doctor.routes.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package doctor
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gorilla/mux"
|
||||
"sismedika.com/sas/westone/services/auth"
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
store types.DoctorStore
|
||||
}
|
||||
|
||||
func NewHandler(store types.DoctorStore) *Handler {
|
||||
return &Handler{store: store}
|
||||
}
|
||||
|
||||
func (h *Handler) RegisterRoutes(router *mux.Router) {
|
||||
doctorroutes := router.PathPrefix("/mddoctor").Subrouter()
|
||||
doctorroutes.Use(auth.AuthMiddleware)
|
||||
|
||||
doctorroutes.HandleFunc("/searchdoctor", h.handlerSearchDoctor).Methods(http.MethodPost)
|
||||
}
|
||||
|
||||
// SearchDoctor searches doctor by name, NIK, Doctor Code, DOB, or phone number
|
||||
//
|
||||
// @Summary Search Doctors listing
|
||||
// @Description Search Doctors by name, NIK, Doctor Code, DOB, or phone number
|
||||
// @Tags Doctor
|
||||
// @Accept json
|
||||
// @Param authorization header string true "Authorization token"
|
||||
// @Param parameter body types.SearchDoctorPayload true "parameter"
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]any
|
||||
// @Failure 400 {object} map[string]any
|
||||
// @Failure 500 {object} map[string]any
|
||||
// @Router /westone/api/v1/mddoctor/searchdoctor [post]
|
||||
func (h *Handler) handlerSearchDoctor(w http.ResponseWriter, r *http.Request) {
|
||||
var payload types.SearchDoctorPayload
|
||||
if err := utils.ParseJSON(r, &payload); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := utils.Validate.Struct(payload); err != nil {
|
||||
erros := err.(validator.ValidationErrors)
|
||||
utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("invalid payload: %v", erros))
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.store.SearchDoctor(payload.Keyword, payload.Page, payload.Perpage)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, response)
|
||||
}
|
||||
76
services/doctor/doctor.store.go
Normal file
76
services/doctor/doctor.store.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package doctor
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"sismedika.com/sas/westone/types"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewStore(db *sqlx.DB) *Store {
|
||||
return &Store{db: db}
|
||||
}
|
||||
|
||||
func (s *Store) SearchDoctor(keyword string, page int, perpage int) ([]types.DoctorResult, error) {
|
||||
var list_doctor []types.DoctorResult
|
||||
var sqlFilter []string
|
||||
|
||||
if keyword != "" {
|
||||
params := strings.Split(keyword, "+")
|
||||
if len(params) > 0 && params[0] != "" {
|
||||
sqlFilter = append(sqlFilter, "PersonName LIKE '%"+params[0]+"%'")
|
||||
}
|
||||
if len(params) > 1 && params[1] != "" {
|
||||
sqlFilter = append(sqlFilter, "PersonIdentifierValue = '"+params[1]+"'")
|
||||
}
|
||||
if len(params) > 2 && params[2] != "" {
|
||||
sqlFilter = append(sqlFilter, "DoctorCode = '"+params[2]+"'")
|
||||
}
|
||||
if len(params) > 3 && params[3] != "" {
|
||||
sqlFilter = append(sqlFilter, "PersonBirthDate = '"+params[3]+"'")
|
||||
}
|
||||
if len(params) > 4 && params[4] != "" {
|
||||
sqlFilter = append(sqlFilter, "PersonTelecomValue = '"+params[4]+"'")
|
||||
}
|
||||
}
|
||||
|
||||
doctor_tb := `DoctorID, DoctorCode, DoctorIHSNumber, DoctorPersonID`
|
||||
|
||||
person_tb := `PersonID, PersonName, PersonSalutation, PersonPrefix, PersonSuffix, PersonGender, PersonBirthPlace, PersonBirthDate,
|
||||
PersonReligionCode, PersonReligionSystem, PersonBloodTypeCode, PersonBloodTypeSystem, PersonRhesusCode, PersonRhesusSystem,
|
||||
PersonEducationCode, PersonEducationSystem, PersonJobClassCode, PersonJobClassSystem, PersonEtnicityCode, PersonEtnicitySystem,
|
||||
PersonMaritalBirth, PersonMaritalStatus`
|
||||
|
||||
person_address_tb := `PersonAddressID, PersonAddressPersonID, PersonAddressIsDefault, PersonAddressUse, PersonAddressType, PersonAddressText,
|
||||
PersonAddressLine1, PersonAddressLine2, PersonAddressRegionalCd, PersonAddressRT, PersonAddressRW, PersonAddressCity,
|
||||
PersonAddressDistrict, PersonAddressState, PersonAddressCountry`
|
||||
|
||||
person_identifier_tb := `PersonIdentifierID, PersonIdentifierPersonID, PersonIdentifierUse, PersonIdentifierTypeCode,
|
||||
PersonIdentifierTypeSystem, PersonIdentifierCode, PersonIdentifierSystem, PersonIdentifierValue`
|
||||
|
||||
person_telecom_tb := `PersonTelecomID, PersonTelecomPersonID, PersonTelecomSystem, PersonTelecomValue, PersonTelecomUse`
|
||||
|
||||
offset := (page - 1) * perpage
|
||||
sql := `SELECT ` + doctor_tb + `,` + person_tb + `,` + person_identifier_tb + `,` + person_address_tb + `,` + person_telecom_tb +
|
||||
` FROM doctor
|
||||
JOIN person ON PersonID = DoctorPersonID
|
||||
JOIN person_identifier ON PersonIdentifierPersonID = PersonID
|
||||
JOIN person_telecom ON PersonTelecomPersonID = PersonID
|
||||
JOIN person_address ON PersonAddressPersonID = PersonID
|
||||
WHERE DoctorIsActive = 'Y'`
|
||||
|
||||
if len(sqlFilter) > 0 {
|
||||
sql += " AND " + strings.Join(sqlFilter, " AND ")
|
||||
}
|
||||
sql += " LIMIT ? OFFSET ?"
|
||||
|
||||
if err := s.db.Select(&list_doctor, sql, perpage, offset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return list_doctor, nil
|
||||
}
|
||||
50
services/error_log/error.store.go
Normal file
50
services/error_log/error.store.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package error_log
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewStore(db *sqlx.DB) *Store {
|
||||
return &Store{db: db}
|
||||
}
|
||||
|
||||
func (s *Store) CreateErrorLog(errval utils.LogError) error {
|
||||
tx, err := s.db.BeginTxx(context.Background(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
qry := `INSERT INTO westone_log.error_log
|
||||
(ErrorLogCode, ErrorTraceID, ErrorLogType, ErrorLogParams, ErrorLogQuery, ErrorMessage, ErrorLogTimestamp)
|
||||
VALUES (:ErrorLogCode, :ErrorTraceID, :ErrorLogType, :ErrorLogParams, :ErrorLogQuery, :ErrorMessage, :ErrorLogTimestamp)`
|
||||
|
||||
_, err = tx.NamedExec(qry, errval)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delqry := `DELETE FROM error_log WHERE ErrorLogTimestamp < DATE_SUB(NOW(), INTERVAL 1 MONTH)`
|
||||
_, err = tx.Exec(delqry)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
82
services/order/store.go
Normal file
82
services/order/store.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package order
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"sismedika.com/sas/westone/types"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewStore(db *sqlx.DB) *Store {
|
||||
return &Store{db: db}
|
||||
}
|
||||
|
||||
func (s *Store) CreateOrder(order types.Order) (int, error) {
|
||||
// Start a new transaction
|
||||
tx, err := s.db.BeginTxx(context.Background(), nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Defer rollback to ensure transaction is rolled back in case of an error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
query := `INSERT INTO orders (userId, total, status, address)
|
||||
VALUES (:userId, :total, :status, :address)`
|
||||
|
||||
// Using tx.NamedExec to insert the order within the transaction
|
||||
res, err := tx.NamedExec(query, order)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Retrieving the last inserted ID
|
||||
id, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Commit the transaction if everything succeeded
|
||||
if err = tx.Commit(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(id), nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateOrderItem(orderItem types.OrderItem) error {
|
||||
// Start a new transaction
|
||||
tx, err := s.db.BeginTxx(context.Background(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Defer rollback to ensure transaction is rolled back in case of an error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
query := `INSERT INTO order_items (orderId, productId, quantity, price)
|
||||
VALUES (:orderId, :productId, :quantity, :price)`
|
||||
|
||||
// Using tx.NamedExec to insert the order item within the transaction
|
||||
_, err = tx.NamedExec(query, orderItem)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Commit the transaction if everything succeeded
|
||||
if err = tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
98
services/patient/patient.routes.go
Normal file
98
services/patient/patient.routes.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package patient
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gorilla/mux"
|
||||
"sismedika.com/sas/westone/services/auth"
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
store types.PatientStore
|
||||
}
|
||||
|
||||
func NewHandler(store types.PatientStore) *Handler {
|
||||
return &Handler{store: store}
|
||||
}
|
||||
|
||||
func (h *Handler) RegisterRoutes(router *mux.Router) {
|
||||
patientroute := router.PathPrefix("/mdpatient").Subrouter()
|
||||
patientroute.Use(auth.AuthMiddleware)
|
||||
|
||||
patientroute.HandleFunc("/searchpatient", h.handlerSearchPatient).Methods(http.MethodPost)
|
||||
patientroute.HandleFunc("/searchpatientold", h.handlerSearchPatientOld).Methods(http.MethodPost)
|
||||
}
|
||||
|
||||
// SearchPatient searches patients by name, NIK, registration number, DOB, or phone number
|
||||
//
|
||||
// @Summary Search patient listing
|
||||
// @Description Search patients by name, NIK, registration number, DOB, or phone number
|
||||
// @Tags Patient
|
||||
// @Accept json
|
||||
// @Param authorization header string true "Authorization token"
|
||||
// @Param parameter body types.SearchPatientPayload true "parameter"
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]any
|
||||
// @Failure 400 {object} map[string]any
|
||||
// @Failure 500 {object} map[string]any
|
||||
// @Router /westone/api/v1/mdpatient/searchpatient [post]
|
||||
func (h *Handler) handlerSearchPatient(w http.ResponseWriter, r *http.Request) {
|
||||
var payload types.SearchPatientPayload
|
||||
if err := utils.ParseJSON(r, &payload); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := utils.Validate.Struct(payload); err != nil {
|
||||
erros := err.(validator.ValidationErrors)
|
||||
utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("invalid payload: %v", erros))
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.store.SearchPatient(payload.Keyword, payload.Page, payload.Perpage)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, response)
|
||||
}
|
||||
|
||||
// SearchPatientOld searches patients by name, NIK, registration number, DOB, or phone number
|
||||
//
|
||||
// @Summary Search patient listing old methods
|
||||
// @Description Search patients by name, NIK, registration number, DOB, or phone number
|
||||
// @Tags Patient
|
||||
// @Accept json
|
||||
// @Param authorization header string true "Authorization token"
|
||||
// @Param parameter body types.SearchPatientPayload true "parameter"
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]any
|
||||
// @Failure 400 {object} map[string]any
|
||||
// @Failure 500 {object} map[string]any
|
||||
// @Router /westone/api/v1/mdpatient/searchpatientold [post]
|
||||
func (h *Handler) handlerSearchPatientOld(w http.ResponseWriter, r *http.Request) {
|
||||
var payload types.SearchPatientPayload
|
||||
if err := utils.ParseJSON(r, &payload); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := utils.Validate.Struct(payload); err != nil {
|
||||
erros := err.(validator.ValidationErrors)
|
||||
utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("invalid payload: %v", erros))
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.store.SearchPatientOld(payload.Keyword, payload.Page, payload.Perpage)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, response)
|
||||
}
|
||||
161
services/patient/patient.store.go
Normal file
161
services/patient/patient.store.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package patient
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"sismedika.com/sas/westone/types"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewStore(db *sqlx.DB) *Store {
|
||||
return &Store{db: db}
|
||||
}
|
||||
|
||||
func (s *Store) GetPatientByID(id int) (*types.Patient, error) {
|
||||
patient := new(types.Patient)
|
||||
|
||||
qry := `
|
||||
SELECT
|
||||
PatientID
|
||||
PatientPersonID,
|
||||
PatientManaginOrganizationID,
|
||||
PatientHISNumber,
|
||||
PatientRegNumber
|
||||
FROM patient
|
||||
WHERE PatientID = ?
|
||||
`
|
||||
if err := s.db.Get(patient, qry, id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return patient, nil
|
||||
}
|
||||
|
||||
func (s *Store) SearchPatient(keyword string, page int, perpage int) ([]types.PatientResult, error) {
|
||||
var list_patient []types.PatientResult
|
||||
var sqlName, sqlNIK, sqlReg, sqlDob, sqlhp string
|
||||
|
||||
// params nama+nik+noreg+dob+nohp
|
||||
if keyword != "" {
|
||||
params := strings.Split(keyword, "+")
|
||||
if len(params) > 0 && params[0] != "" {
|
||||
// sqlName = " AND PersonName LIKE '%" + params[0] + "%' "
|
||||
sqlName = " WHERE MATCH(PersonNameFulltext) AGAINST('" + params[0] + "' IN BOOLEAN MODE) "
|
||||
}
|
||||
if len(params) > 1 && params[1] != "" {
|
||||
sqlNIK = " AND PersonIdentifierValue = '" + params[1] + "' "
|
||||
}
|
||||
if len(params) > 2 && params[2] != "" {
|
||||
sqlReg = " AND PatientRegNumber = '" + params[2] + "' "
|
||||
}
|
||||
if len(params) > 3 && params[3] != "" {
|
||||
sqlDob = " AND PersonBirthDate = '" + params[3] + "' "
|
||||
}
|
||||
if len(params) > 4 && params[4] != "" {
|
||||
sqlhp = " AND PersonTelecomValue = '" + params[4] + "' "
|
||||
}
|
||||
}
|
||||
|
||||
patient_tb := `PatientID, PatientPersonID, PatientManaginOrganizationID, PatientHISNumber, PatientRegNumber`
|
||||
|
||||
person_tb := `PersonID, PersonName, PersonSalutation, PersonPrefix, PersonSuffix, PersonGender, PersonBirthPlace, PersonBirthDate,
|
||||
PersonReligionCode, PersonReligionSystem, PersonBloodTypeCode, PersonBloodTypeSystem, PersonRhesusCode, PersonRhesusSystem,
|
||||
PersonEducationCode, PersonEducationSystem, PersonJobClassCode, PersonJobClassSystem, PersonEtnicityCode, PersonEtnicitySystem,
|
||||
PersonMaritalBirth, PersonMaritalStatus`
|
||||
|
||||
person_address_tb := `PersonAddressID, PersonAddressPersonID, PersonAddressIsDefault, PersonAddressUse, PersonAddressType, PersonAddressText,
|
||||
PersonAddressLine1, PersonAddressLine2, PersonAddressRegionalCd, PersonAddressRT, PersonAddressRW, PersonAddressCity,
|
||||
PersonAddressDistrict, PersonAddressState, PersonAddressCountry`
|
||||
|
||||
person_identifier_tb := `PersonIdentifierID, PersonIdentifierPersonID, PersonIdentifierUse, PersonIdentifierTypeCode,
|
||||
PersonIdentifierTypeSystem, PersonIdentifierCode, PersonIdentifierSystem, PersonIdentifierValue`
|
||||
|
||||
person_telecom_tb := `PersonTelecomID, PersonTelecomPersonID, PersonTelecomSystem, PersonTelecomValue, PersonTelecomUse`
|
||||
|
||||
offset := (page - 1) * perpage
|
||||
sql := `SELECT ` + patient_tb + `,` + person_tb + `,` + person_identifier_tb + `,` + person_address_tb + `,` + person_telecom_tb +
|
||||
` FROM patient
|
||||
JOIN person ON PersonID = PatientPersonID AND PatientIsActive = 'Y'` + sqlDob + sqlReg + `
|
||||
JOIN person_identifier ON PersonIdentifierPersonID = PersonID` + sqlNIK + `
|
||||
JOIN person_telecom ON PersonTelecomPersonID = PersonID` + sqlhp + `
|
||||
JOIN person_address ON PersonAddressPersonID = PersonID
|
||||
` + sqlName + `
|
||||
LIMIT ? OFFSET ?`
|
||||
|
||||
if err := s.db.Select(&list_patient, sql, perpage, offset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return list_patient, nil
|
||||
}
|
||||
|
||||
func (s *Store) SearchPatientOld(keyword string, page int, perpage int) ([]types.PatientResult, error) {
|
||||
var list_patient []types.PatientResult
|
||||
// var sqlFilter []string
|
||||
var sqlName, sqlNIK, sqlReg, sqlDob, sqlhp string
|
||||
|
||||
// params nama+nik+noreg+dob+nohp
|
||||
if keyword != "" {
|
||||
params := strings.Split(keyword, "+")
|
||||
if len(params) > 0 && params[0] != "" {
|
||||
// sqlFilter = append(sqlFilter, "PersonName LIKE '%"+params[0]+"%'")
|
||||
sqlName = " AND PersonName LIKE '%" + params[0] + "%' "
|
||||
}
|
||||
if len(params) > 1 && params[1] != "" {
|
||||
// sqlFilter = append(sqlFilter, "PersonIdentifierValue = '"+params[1]+"'")
|
||||
sqlNIK = " AND PersonIdentifierValue = '" + params[1] + "' "
|
||||
}
|
||||
if len(params) > 2 && params[2] != "" {
|
||||
// sqlFilter = append(sqlFilter, "PatientRegNumber = '"+params[2]+"'")
|
||||
sqlReg = " AND PatientRegNumber = '" + params[2] + "' "
|
||||
}
|
||||
if len(params) > 3 && params[3] != "" {
|
||||
// sqlFilter = append(sqlFilter, "PersonBirthDate = '"+params[3]+"'")
|
||||
sqlDob = " AND PersonBirthDate = '" + params[3] + "' "
|
||||
}
|
||||
if len(params) > 4 && params[4] != "" {
|
||||
// sqlFilter = append(sqlFilter, "PersonTelecomValue = '"+params[4]+"'")
|
||||
sqlhp = " AND PersonTelecomValue = '" + params[4] + "' "
|
||||
}
|
||||
}
|
||||
|
||||
patient_tb := `PatientID, PatientPersonID, PatientManaginOrganizationID, PatientHISNumber, PatientRegNumber`
|
||||
|
||||
person_tb := `PersonID, PersonName, PersonSalutation, PersonPrefix, PersonSuffix, PersonGender, PersonBirthPlace, PersonBirthDate,
|
||||
PersonReligionCode, PersonReligionSystem, PersonBloodTypeCode, PersonBloodTypeSystem, PersonRhesusCode, PersonRhesusSystem,
|
||||
PersonEducationCode, PersonEducationSystem, PersonJobClassCode, PersonJobClassSystem, PersonEtnicityCode, PersonEtnicitySystem,
|
||||
PersonMaritalBirth, PersonMaritalStatus`
|
||||
|
||||
person_address_tb := `PersonAddressID, PersonAddressPersonID, PersonAddressIsDefault, PersonAddressUse, PersonAddressType, PersonAddressText,
|
||||
PersonAddressLine1, PersonAddressLine2, PersonAddressRegionalCd, PersonAddressRT, PersonAddressRW, PersonAddressCity,
|
||||
PersonAddressDistrict, PersonAddressState, PersonAddressCountry`
|
||||
|
||||
person_identifier_tb := `PersonIdentifierID, PersonIdentifierPersonID, PersonIdentifierUse, PersonIdentifierTypeCode,
|
||||
PersonIdentifierTypeSystem, PersonIdentifierCode, PersonIdentifierSystem, PersonIdentifierValue`
|
||||
|
||||
person_telecom_tb := `PersonTelecomID, PersonTelecomPersonID, PersonTelecomSystem, PersonTelecomValue, PersonTelecomUse`
|
||||
|
||||
offset := (page - 1) * perpage
|
||||
sql := `SELECT ` + patient_tb + `,` + person_tb + `,` + person_identifier_tb + `,` + person_address_tb + `,` + person_telecom_tb +
|
||||
` FROM patient
|
||||
JOIN person ON PersonID = PatientPersonID AND PatientIsActive = 'Y'` + sqlName + sqlDob + sqlReg + `
|
||||
JOIN person_identifier ON PersonIdentifierPersonID = PersonID` + sqlNIK + `
|
||||
JOIN person_telecom ON PersonTelecomPersonID = PersonID` + sqlhp + `
|
||||
JOIN person_address ON PersonAddressPersonID = PersonID
|
||||
LIMIT ? OFFSET ?`
|
||||
|
||||
// if len(sqlFilter) > 0 {
|
||||
// sql += " AND " + strings.Join(sqlFilter, " AND ")
|
||||
// }
|
||||
// sql += " "
|
||||
|
||||
if err := s.db.Select(&list_patient, sql, perpage, offset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return list_patient, nil
|
||||
}
|
||||
57
services/person/person.routes.go
Normal file
57
services/person/person.routes.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package person
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"sismedika.com/sas/westone/services/auth"
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
store types.PersonStore
|
||||
terminology types.TerminologyStore
|
||||
}
|
||||
|
||||
func NewHandler(store types.PersonStore, terminology types.TerminologyStore) *Handler {
|
||||
return &Handler{store: store, terminology: terminology}
|
||||
}
|
||||
|
||||
func (h *Handler) RegisterRoutes(router *mux.Router) {
|
||||
personroute := router.PathPrefix("/mdperson").Subrouter()
|
||||
personroute.Use(auth.AuthMiddleware)
|
||||
|
||||
personroute.HandleFunc("/get/{personID}", h.handleGetPersonByID).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
func (h *Handler) handleGetPersonByID(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
param, ok := vars["personID"]
|
||||
if !ok {
|
||||
utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("missing params person id"))
|
||||
return
|
||||
}
|
||||
|
||||
personID, err := strconv.Atoi(param)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("invalid params person id"))
|
||||
return
|
||||
}
|
||||
|
||||
person, err := h.store.GetPersonByID(personID)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
json, err := h.getDisplayTerminology(*person)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, json)
|
||||
}
|
||||
66
services/person/person.services.go
Normal file
66
services/person/person.services.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package person
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
func (h *Handler) getDisplayTerminology(person types.Person) (*types.PersonJSON, error) {
|
||||
ret := new(types.PersonJSON)
|
||||
err := utils.MatchStruct(person, ret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if person.PersonBloodTypeCode != "" {
|
||||
blood, err := h.terminology.GetTerminologyDisplay(person.PersonBloodTypeCode, person.PersonBloodTypeSystem)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("blood")
|
||||
}
|
||||
ret.PersonBlood = *blood
|
||||
}
|
||||
|
||||
if person.PersonEducationCode != "" {
|
||||
education, err := h.terminology.GetTerminologyDisplay(person.PersonEducationCode, person.PersonEducationSystem)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("edu")
|
||||
}
|
||||
ret.PersonEducation = *education
|
||||
}
|
||||
|
||||
if person.PersonEtnicityCode != "" {
|
||||
etnicity, err := h.terminology.GetTerminologyDisplay(person.PersonEtnicityCode, person.PersonEtnicitySystem)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("etni")
|
||||
}
|
||||
ret.PersonEtnicity = *etnicity
|
||||
}
|
||||
|
||||
if person.PersonJobClassCode != "" {
|
||||
job, err := h.terminology.GetTerminologyDisplay(person.PersonJobClassCode, person.PersonJobClassSystem)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("job")
|
||||
}
|
||||
ret.PersonJobClass = *job
|
||||
}
|
||||
|
||||
if person.PersonReligionCode != "" {
|
||||
religion, err := h.terminology.GetTerminologyDisplay(person.PersonReligionCode, person.PersonReligionSystem)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reli")
|
||||
}
|
||||
ret.PersonReligion = *religion
|
||||
}
|
||||
|
||||
if person.PersonRhesusCode != "" {
|
||||
rhesus, err := h.terminology.GetTerminologyDisplay(person.PersonRhesusCode, person.PersonRhesusSystem)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("rhes")
|
||||
}
|
||||
ret.PersonRhesus = *rhesus
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
50
services/person/person.store.go
Normal file
50
services/person/person.store.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package person
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"sismedika.com/sas/westone/types"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewStore(db *sqlx.DB) *Store {
|
||||
return &Store{db: db}
|
||||
}
|
||||
|
||||
func (s *Store) GetPersonByID(id int) (*types.Person, error) {
|
||||
person := new(types.Person)
|
||||
|
||||
qry := `
|
||||
SELECT
|
||||
PersonID,
|
||||
PersonName,
|
||||
PersonSalutation,
|
||||
PersonPrefix,
|
||||
PersonSuffix,
|
||||
PersonGender,
|
||||
PersonBirthPlace,
|
||||
PersonBirthDate,
|
||||
PersonReligionCode,
|
||||
PersonReligionSystem,
|
||||
PersonBloodTypeCode,
|
||||
PersonBloodTypeSystem,
|
||||
PersonRhesusCode,
|
||||
PersonRhesusSystem,
|
||||
PersonEducationCode,
|
||||
PersonEducationSystem,
|
||||
PersonJobClassCode,
|
||||
PersonJobClassSystem,
|
||||
PersonEtnicityCode,
|
||||
PersonEtnicitySystem,
|
||||
PersonMaritalBirth,
|
||||
PersonMaritalStatus
|
||||
FROM person WHERE PersonID = ?
|
||||
`
|
||||
if err := s.db.Get(person, qry, id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return person, nil
|
||||
}
|
||||
84
services/product/routes.go
Normal file
84
services/product/routes.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gorilla/mux"
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
store types.ProductStore
|
||||
userStore types.UserStore
|
||||
}
|
||||
|
||||
func NewHandler(store types.ProductStore, userStore types.UserStore) *Handler {
|
||||
return &Handler{store: store, userStore: userStore}
|
||||
}
|
||||
|
||||
func (h *Handler) RegisterRoutes(router *mux.Router) {
|
||||
router.HandleFunc("/products", h.handleGetProducts).Methods(http.MethodGet)
|
||||
router.HandleFunc("/products/{productID}", h.handleGetProduct).Methods(http.MethodGet)
|
||||
|
||||
// admin routes
|
||||
// router.HandleFunc("/products", auth.WithJWTAuth(h.handleCreateProduct)).Methods(http.MethodPost)
|
||||
}
|
||||
|
||||
func (h *Handler) handleGetProducts(w http.ResponseWriter, r *http.Request) {
|
||||
products, err := h.store.GetProducts()
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, products)
|
||||
}
|
||||
|
||||
func (h *Handler) handleGetProduct(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
str, ok := vars["productID"]
|
||||
if !ok {
|
||||
utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("missing product ID"))
|
||||
return
|
||||
}
|
||||
|
||||
productID, err := strconv.Atoi(str)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("invalid product ID"))
|
||||
return
|
||||
}
|
||||
|
||||
product, err := h.store.GetProductByID(productID)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, product)
|
||||
}
|
||||
|
||||
func (h *Handler) handleCreateProduct(w http.ResponseWriter, r *http.Request) {
|
||||
var product types.CreateProductPayload
|
||||
if err := utils.ParseJSON(r, &product); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := utils.Validate.Struct(product); err != nil {
|
||||
errors := err.(validator.ValidationErrors)
|
||||
utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("invalid payload: %v", errors))
|
||||
return
|
||||
}
|
||||
|
||||
err := h.store.CreateProduct(product)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusCreated, product)
|
||||
}
|
||||
157
services/product/routes_test.go
Normal file
157
services/product/routes_test.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"sismedika.com/sas/westone/types"
|
||||
)
|
||||
|
||||
func TestProductServiceHandlers(t *testing.T) {
|
||||
productStore := &mockProductStore{}
|
||||
userStore := &mockUserStore{}
|
||||
handler := NewHandler(productStore, userStore)
|
||||
|
||||
t.Run("should handle get products", func(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet, "/products", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/products", handler.handleGetProducts).Methods(http.MethodGet)
|
||||
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Errorf("expected status code %d, got %d", http.StatusOK, rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should fail if the product ID is not a number", func(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet, "/products/abc", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/products/{productID}", handler.handleGetProduct).Methods(http.MethodGet)
|
||||
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status code %d, got %d", http.StatusBadRequest, rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should handle get product by ID", func(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet, "/products/42", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/products/{productID}", handler.handleGetProduct).Methods(http.MethodGet)
|
||||
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Errorf("expected status code %d, got %d", http.StatusOK, rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should fail creating a product if the payload is missing", func(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodPost, "/products", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/products", handler.handleCreateProduct).Methods(http.MethodPost)
|
||||
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status code %d, got %d", http.StatusBadRequest, rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should handle creating a product", func(t *testing.T) {
|
||||
payload := types.CreateProductPayload{
|
||||
Name: "test",
|
||||
Price: 100,
|
||||
Image: "test.jpg",
|
||||
Description: "test description",
|
||||
Quantity: 10,
|
||||
}
|
||||
|
||||
marshalled, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, "/products", bytes.NewBuffer(marshalled))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/products", handler.handleCreateProduct).Methods(http.MethodPost)
|
||||
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusCreated {
|
||||
t.Errorf("expected status code %d, got %d", http.StatusCreated, rr.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type mockProductStore struct{}
|
||||
|
||||
func (m *mockProductStore) GetProductByID(productID int) (*types.Product, error) {
|
||||
return &types.Product{}, nil
|
||||
}
|
||||
|
||||
func (m *mockProductStore) GetProducts() ([]*types.Product, error) {
|
||||
return []*types.Product{}, nil
|
||||
}
|
||||
|
||||
func (m *mockProductStore) CreateProduct(product types.CreateProductPayload) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockProductStore) UpdateProduct(product types.Product) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockProductStore) GetProductsByID(ids []int) ([]types.Product, error) {
|
||||
return []types.Product{}, nil
|
||||
}
|
||||
|
||||
type mockUserStore struct{}
|
||||
|
||||
func (m *mockUserStore) GetUserByID(userID int) (*types.User, error) {
|
||||
return &types.User{}, nil
|
||||
}
|
||||
|
||||
func (m *mockUserStore) CreateUser(user types.User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockUserStore) SignIn(email string, password string) (*types.User, error) {
|
||||
return &types.User{}, nil
|
||||
}
|
||||
101
services/product/store.go
Normal file
101
services/product/store.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"sismedika.com/sas/westone/types"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewStore(db *sqlx.DB) *Store {
|
||||
return &Store{db: db}
|
||||
}
|
||||
|
||||
func (s *Store) GetProductByID(productID int) (*types.Product, error) {
|
||||
product := new(types.Product)
|
||||
query := "SELECT * FROM products WHERE id = ?"
|
||||
|
||||
// sqlx.Get is used for fetching a single record and scanning it into the struct
|
||||
if err := s.db.Get(product, query, productID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return product, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetProductsByID(productIDs []int) ([]types.Product, error) {
|
||||
query, args, err := sqlx.In("SELECT * FROM products WHERE id IN (?)", productIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Rebind the query for the specific database driver (e.g., mysql uses "?", postgres uses "$1", etc.)
|
||||
query = s.db.Rebind(query)
|
||||
|
||||
var products []types.Product
|
||||
err = s.db.Select(&products, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return products, nil
|
||||
}
|
||||
|
||||
func (s *Store) GetProducts() ([]*types.Product, error) {
|
||||
var products []*types.Product
|
||||
query := "SELECT * FROM products"
|
||||
|
||||
// sqlx.Select is used to fetch multiple rows and map them into a slice of structs
|
||||
if err := s.db.Select(&products, query); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return products, nil
|
||||
}
|
||||
|
||||
func (s *Store) CreateProduct(product types.CreateProductPayload) error {
|
||||
query := `INSERT INTO products (name, price, image, description, quantity)
|
||||
VALUES (:name, :price, :image, :description, :quantity)`
|
||||
|
||||
// Using sqlx.NamedExec to map struct fields to named parameters in the query
|
||||
_, err := s.db.NamedExec(query, product)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (s *Store) UpdateProduct(product types.Product) error {
|
||||
// Start a new transaction
|
||||
tx, err := s.db.BeginTxx(context.Background(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Defer rollback to ensure transaction is rolled back in case of an error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
query := `UPDATE products SET name = :name, price = :price, image = :image,
|
||||
description = :description, quantity = :quantity WHERE id = :id`
|
||||
|
||||
// Using tx.NamedExec to execute the update within the transaction
|
||||
_, err = tx.NamedExec(query, product)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Commit the transaction if everything succeeded
|
||||
if err = tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
54
services/terminology/terminology.routes.go
Normal file
54
services/terminology/terminology.routes.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package terminology
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gorilla/mux"
|
||||
"sismedika.com/sas/westone/services/auth"
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
store types.TerminologyStore
|
||||
}
|
||||
|
||||
func NewHandler(store types.TerminologyStore) *Handler {
|
||||
return &Handler{store: store}
|
||||
}
|
||||
|
||||
func (h *Handler) RegisterRoutes(router *mux.Router) {
|
||||
router.HandleFunc("/termicheck", h.checkAPI).Methods(http.MethodGet)
|
||||
termi := router.PathPrefix("/terminology").Subrouter()
|
||||
termi.Use(auth.AuthMiddleware)
|
||||
|
||||
termi.HandleFunc("/getdisplay", h.handleTerminologyDisplay).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
func (h *Handler) checkAPI(w http.ResponseWriter, _ *http.Request) {
|
||||
utils.WriteJSON(w, http.StatusOK, "Connected")
|
||||
}
|
||||
|
||||
func (h *Handler) handleTerminologyDisplay(w http.ResponseWriter, r *http.Request) {
|
||||
var payload types.GetTerminologyDisplayPayload
|
||||
if err := utils.ParseJSON(r, &payload); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := utils.Validate.Struct(payload); err != nil {
|
||||
erros := err.(validator.ValidationErrors)
|
||||
utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("invalid payload: %v", erros))
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.store.GetTerminologyDisplay(payload.Code, payload.CodeSystem)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, response)
|
||||
}
|
||||
29
services/terminology/terminology.store.go
Normal file
29
services/terminology/terminology.store.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package terminology
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"sismedika.com/sas/westone/types"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewStore(db *sqlx.DB) *Store {
|
||||
return &Store{db: db}
|
||||
}
|
||||
|
||||
func (s *Store) GetTerminologyDisplay(code string, system string) (*types.CodeableTerminology, error) {
|
||||
term := new(types.CodeableTerminology)
|
||||
|
||||
qry := `
|
||||
SELECT code, display, code_system
|
||||
FROM terminology
|
||||
WHERE code = ? AND code_system = ?
|
||||
`
|
||||
if err := s.db.Get(term, qry, code, system); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return term, nil
|
||||
}
|
||||
123
services/user/user.routes.go
Normal file
123
services/user/user.routes.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/gorilla/mux"
|
||||
"sismedika.com/sas/westone/services/auth"
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
store types.UserStore
|
||||
errorStore types.ErrorLogStore
|
||||
}
|
||||
|
||||
func NewHandler(store types.UserStore, errorStore types.ErrorLogStore) *Handler {
|
||||
return &Handler{
|
||||
store: store,
|
||||
errorStore: errorStore,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) RegisterRoutes(router *mux.Router) {
|
||||
router.HandleFunc("/login", h.handleLogin).Methods("POST")
|
||||
router.HandleFunc("/register", h.handleRegister).Methods("POST")
|
||||
|
||||
// admin routes
|
||||
// router.HandleFunc("/users/{userID}", auth.WithJWTAuth(h.handleGetUser)).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
// var payload types.SignInPayload
|
||||
// if err := utils.ParseJSON(r, &payload); err != nil {
|
||||
// utils.WriteError(w, http.StatusBadRequest, err)
|
||||
// return
|
||||
// }
|
||||
|
||||
// if err := utils.Validate.Struct(payload); err != nil {
|
||||
// errors := err.(validator.ValidationErrors)
|
||||
// utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("invalid payload: %v", errors))
|
||||
// return
|
||||
// }
|
||||
|
||||
// hashedPassword := auth.HashWithMD5(payload.Password)
|
||||
// response, err := h.store.SignIn(payload.Email, hashedPassword)
|
||||
// if err != nil {
|
||||
// var logError *utils.LogError
|
||||
// if errors.As(err, &logError) {
|
||||
// h.errorStore.CreateErrorLog(*logError)
|
||||
// utils.WriteErrorLog(w, http.StatusBadRequest, *logError)
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
// // remoteAddr := r.RemoteAddr
|
||||
// // userAgent := r.UserAgent()
|
||||
// // response.IP = remoteAddr
|
||||
// // response.Agent = userAgent
|
||||
|
||||
// secret := []byte(configs.Envs.JWTSecret)
|
||||
// token, err := auth.CreateJWT(secret, *response)
|
||||
// if err != nil {
|
||||
// utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
// return
|
||||
// }
|
||||
|
||||
// utils.WriteJSONLogin(w, http.StatusOK, response, token, "westone")
|
||||
}
|
||||
|
||||
func (h *Handler) handleRegister(w http.ResponseWriter, r *http.Request) {
|
||||
// var user types.RegisterUserPayload
|
||||
// if err := utils.ParseJSON(r, &user); err != nil {
|
||||
// utils.WriteError(w, http.StatusBadRequest, err)
|
||||
// return
|
||||
// }
|
||||
|
||||
// if err := utils.Validate.Struct(user); err != nil {
|
||||
// errors := err.(validator.ValidationErrors)
|
||||
// utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("invalid payload: %v", errors))
|
||||
// return
|
||||
// }
|
||||
|
||||
// // check if user exists
|
||||
// _, err := h.store.GetUserByEmail(user.Email)
|
||||
// if err == nil {
|
||||
// utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("user with email %s already exists", user.Email))
|
||||
// return
|
||||
// }
|
||||
|
||||
// // hash password
|
||||
// hashedPassword, err := auth.HashPassword(user.Password)
|
||||
// if err != nil {
|
||||
// utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
// return
|
||||
// }
|
||||
|
||||
// err = h.store.CreateUser(types.User{
|
||||
// FirstName: user.FirstName,
|
||||
// LastName: user.LastName,
|
||||
// Email: user.Email,
|
||||
// Password: hashedPassword,
|
||||
// })
|
||||
// if err != nil {
|
||||
// utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
// return
|
||||
// }
|
||||
|
||||
// utils.WriteJSON(w, http.StatusCreated, nil)
|
||||
}
|
||||
|
||||
func (h *Handler) handleGetUser(w http.ResponseWriter, r *http.Request) {
|
||||
claims, ok := r.Context().Value(auth.UserContextKey).(jwt.MapClaims)
|
||||
if !ok {
|
||||
utils.WriteError(w, http.StatusInternalServerError, fmt.Errorf("token not found in context"))
|
||||
return
|
||||
}
|
||||
|
||||
email := claims["M_UserEmail"]
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, email)
|
||||
}
|
||||
77
services/user/user.routes_test.go
Normal file
77
services/user/user.routes_test.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
func TestUserServiceHandlers(t *testing.T) {
|
||||
userStore := &mockUserStore{}
|
||||
errorStore := &mockErrorLogStore{}
|
||||
handler := NewHandler(userStore, errorStore)
|
||||
|
||||
t.Run("should fail if the user ID is not a number", func(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet, "/user/abc", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/user/{userID}", handler.handleGetUser).Methods(http.MethodGet)
|
||||
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status code %d, got %d", http.StatusBadRequest, rr.Code)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("should handle get user by ID", func(t *testing.T) {
|
||||
req, err := http.NewRequest(http.MethodGet, "/user/42", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/user/{userID}", handler.handleGetUser).Methods(http.MethodGet)
|
||||
|
||||
router.ServeHTTP(rr, req)
|
||||
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Errorf("expected status code %d, got %d", http.StatusOK, rr.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type mockUserStore struct{}
|
||||
|
||||
func (m *mockUserStore) UpdateUser(u types.User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockUserStore) SignIn(email string, password string) (*types.User, error) {
|
||||
return &types.User{}, nil
|
||||
}
|
||||
|
||||
func (m *mockUserStore) CreateUser(u types.User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockUserStore) GetUserByID(id int) (*types.User, error) {
|
||||
return &types.User{}, nil
|
||||
}
|
||||
|
||||
type mockErrorLogStore struct{}
|
||||
|
||||
func (n *mockErrorLogStore) CreateErrorLog(errval utils.LogError) error {
|
||||
return nil
|
||||
}
|
||||
70
services/user/user.store.go
Normal file
70
services/user/user.store.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"sismedika.com/sas/westone/types"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewStore(db *sqlx.DB) *Store {
|
||||
return &Store{db: db}
|
||||
}
|
||||
func (s *Store) CreateUser(user types.User) error {
|
||||
// Start a new transaction
|
||||
// tx, err := s.db.BeginTxx(context.Background(), nil)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// // Defer rollback to ensure the transaction is rolled back in case of an error
|
||||
// defer func() {
|
||||
// if err != nil {
|
||||
// tx.Rollback()
|
||||
// }
|
||||
// }()
|
||||
|
||||
// query := `INSERT INTO users (firstName, lastName, email, password)
|
||||
// VALUES (:firstName, :lastName, :email, :password)`
|
||||
|
||||
// // Using tx.NamedExec to insert the user within the transaction
|
||||
// _, err = tx.NamedExec(query, user)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// // Commit the transaction if everything succeeded
|
||||
// if err = tx.Commit(); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) GetUserByID(id int) (*types.User, error) {
|
||||
user := new(types.User)
|
||||
|
||||
qryget := `
|
||||
SELECT
|
||||
M_UserID,
|
||||
M_UserEmail,
|
||||
M_UserEmail as M_UserUsername,
|
||||
M_UserGroupDashboard,
|
||||
1 as M_UserDefaultT_SampleStationID,
|
||||
IFNULL(M_UserFullName, "") as M_StaffName,
|
||||
IFNULL(M_StaffIsCourier, "N") as is_courier,
|
||||
IFNULL(S_SystemsAutoLogoutTime,0) as time_autologout
|
||||
FROM m_user
|
||||
JOIN m_usergroup ON M_UserM_UserGroupID = M_UserGroupID
|
||||
LEFT JOIN m_staff on M_UserM_StaffID = M_StaffID
|
||||
JOIN conf_systems ON S_SystemsIsActive = 'Y'
|
||||
WHERE M_UserID = ?
|
||||
`
|
||||
if err := s.db.Get(user, qryget, id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
Reference in New Issue
Block a user