From 47816debd3f00e7aa75bf075d6054eaac3d73cd3 Mon Sep 17 00:00:00 2001 From: linhdevtran99 Date: Wed, 6 Dec 2023 20:27:34 +0700 Subject: [PATCH] prepair to using go concurrent --- .env | 4 +- go.mod | 3 + go.sum | 6 ++ models/models.go | 2 + rest-api/init-api.go | 46 ++++++++----- rest-api/routes/auth.go | 35 +++++++--- rest-api/services/register.go | 121 ++++++++++++++++++-------------- utils/crypto.go | 126 ++++++++++++++++++---------------- utils/mongodb.go | 2 + utils/restapiType.go | 16 ++++- 10 files changed, 222 insertions(+), 139 deletions(-) diff --git a/.env b/.env index 080241e..7de2f47 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ MONGO_URI="mongodb://adminLinh:linhporo1@localhost:27017" API_TEST_PORT=":8080" -REDIS_URI="redis://localhost:6479/0" +REDIS_URI="redis://localhost:6379/0" STMP_PASS="btmp judz ebys pfxw" -EMAIL_VERIFY_SECRET="WDc&4+&vYP(n'}?LHNE#5M?IE|g(c812" \ No newline at end of file +EMAIL_VERIFY_SECRET="WDc&4+&vYP(n'}?LHNE#5M?IE|g(c812" diff --git a/go.mod b/go.mod index 2f166e1..a6061ba 100644 --- a/go.mod +++ b/go.mod @@ -11,14 +11,17 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.16.0 // indirect + github.com/golang-jwt/jwt/v5 v5.2.0 // indirect github.com/golang/snappy v0.0.1 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/joho/godotenv v1.5.1 // indirect github.com/klauspost/compress v1.13.6 // indirect github.com/leodido/go-urn v1.2.4 // indirect + github.com/mervick/aes-everywhere/go/aes256 v0.0.0-20220903070135-f13ed3789ae1 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/pquerna/otp v1.4.0 // indirect github.com/redis/go-redis/v9 v9.3.0 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect diff --git a/go.sum b/go.sum index d403131..b55bb56 100644 --- a/go.sum +++ b/go.sum @@ -18,6 +18,8 @@ github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqR github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= @@ -29,6 +31,8 @@ github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQ github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/mervick/aes-everywhere/go/aes256 v0.0.0-20220903070135-f13ed3789ae1 h1:RLYNaO6dcj6hms2FSzwsXlZsyjxQBJi8qO/8Vkilhz0= +github.com/mervick/aes-everywhere/go/aes256 v0.0.0-20220903070135-f13ed3789ae1/go.mod h1:Eb5RMoo9kOQra/2uRiUTGP+LfNuM13Vqm7y7P34+KKo= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -36,6 +40,8 @@ github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0= github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= diff --git a/models/models.go b/models/models.go index 2a1baac..47d14eb 100644 --- a/models/models.go +++ b/models/models.go @@ -16,6 +16,7 @@ type PreusersMongo struct { HashPassword string `bson:"hash_password" json:"hash_password"` PhoneNumber string `bson:"phone_number" json:"phone_number"` CreatedDate time.Time `bson:"created_date" json:"created_date"` + UpdateDate time.Time `bson:"update_date" json:"update_date"` VerifySentCount int `bson:"verify_sent_count" json:"verify_sent_count"` } @@ -27,6 +28,7 @@ type UsersMongo struct { PhoneNumber string `bson:"phone_number" json:"phone_number"` Active bool `bson:"active" json:"active"` CreatedDate time.Time `bson:"created_date" json:"created_date"` + UpdateDate time.Time `bson:"update_date" json:"update_date"` VerifySentCount int `bson:"verify_sent_count" json:"verify_sent_count"` } diff --git a/rest-api/init-api.go b/rest-api/init-api.go index aab42d9..7a159e3 100644 --- a/rest-api/init-api.go +++ b/rest-api/init-api.go @@ -3,9 +3,13 @@ package rest_api import ( "fmt" "github.com/gorilla/mux" + "linhdevtran99/rest-api/models" "linhdevtran99/rest-api/rest-api/routes" + "linhdevtran99/rest-api/rest-api/services" + "linhdevtran99/rest-api/utils" "log" "net/http" + "time" ) type APIServer struct { @@ -30,11 +34,13 @@ func (s *APIServer) Run() { router := mux.NewRouter() routes.AuthRouterSetup(router) + + router.HandleFunc("/test", utils.MakeHTTPHandlerFn(s.TestRoute)) + startMuxServer(s, router) } -func (s *APIServer) AuthRoute(w http.ResponseWriter, r *http.Request) error { - w.Header().Set("Content-Type", "application/json") +func (s *APIServer) TestRoute(w http.ResponseWriter, r *http.Request) error { if r.Method == http.MethodGet { fmt.Println("hit") @@ -56,23 +62,29 @@ func (s *APIServer) AuthRoute(w http.ResponseWriter, r *http.Request) error { //} //serect := os.Getenv("EMAIL_VERIFY_SECRET") - //isPass, _ := generatorOtp("hello", "nhocdl.poro1@gmail.com", 12, serect) - //fmt.Println(isPass) - //cipherBase64 := createLinkVerify(&models.CreateUser{ - // Username: "thewind121212", - // Email: "nhocdl.poro1@gmail.com", - // Password: "linhporoQ1@", - // ConfirmPassword: "linhporoQ1@", - //}, serect) + + //_, otp := services.GeneratorOtp("hello", "nhocdl.poro1@gmail.com", 12, serect) + //fmt.Println(otp.HashOTP) + //fmt.Println(otp.PureOTP) + + //utils.EncryptAESMailLink("nhocdl.poro2@gmail.com", serect, w) + + //jsonData, _ := json.Marshal(map[string]string{"email": "nhocdl.poro1@gmail.com", "user": "thewind121212"}) + //// + //utils.Redis.Set(context.Background(), "otp:nhocdl.poro1@gmail.com", string(jsonData), time.Minute) // - //key := []byte(serect) + //utils.CheckAndWriteRedis("nhocdl.poro1@gmail.com", "thewind121212", "lasdjflasdjlfj") + + services.CheckAndWritePreuser(&models.PreusersMongo{ + Username: "thewind121212", + Email: "nhocdl.poro2@gmail.com", + PhoneNumber: "0918327132", + VerifySentCount: 0, + CreatedDate: time.Now(), + UpdateDate: time.Now(), + }) + // - //i, err := utils.DecryptAES(cipherBase64, key) - //if err != nil { - // return err - //} - // - //fmt.Println(string(i)) } diff --git a/rest-api/routes/auth.go b/rest-api/routes/auth.go index 8997f70..a18e996 100644 --- a/rest-api/routes/auth.go +++ b/rest-api/routes/auth.go @@ -2,28 +2,47 @@ package routes import ( "encoding/json" - "errors" "github.com/gorilla/mux" "linhdevtran99/rest-api/models" "linhdevtran99/rest-api/rest-api/services" "linhdevtran99/rest-api/utils" "net/http" + "time" ) func RegisterNewAccount(w http.ResponseWriter, r *http.Request) error { - client := utils.MongoDB if r.Method == http.MethodPost { var registerInfo models.CreateUser _ = json.NewDecoder(r.Body).Decode(®isterInfo) //call function check info user type in - validRegisterInfo := services.CheckAndValidRegisterFiled(®isterInfo, w) - //call function check data user use in past or not - isValidData := services.CheckAccountExist(client, registerInfo.Username, registerInfo.Email, w) - - if isValidData == false || validRegisterInfo == false { - return errors.New("USER DON'T HAVE VALID INFO FOR REGISTER ACCOUNT") + validRegisterInfo, responseAPI := services.CheckAndValidRegisterFiled(®isterInfo) + if responseAPI != nil { + return utils.WriteJSON(w, responseAPI.Code, responseAPI.Err.Error()) } + //call function check data user use in past or not + isValidData, responseAPI := services.CheckAccountExist(utils.MongoDB, registerInfo.Username, registerInfo.Email) + if responseAPI != nil { + return utils.WriteJSON(w, responseAPI.Code, responseAPI.Err.Error()) + } + + preUserData := &models.PreusersMongo{ + Username: registerInfo.Username, + Email: registerInfo.Email, + PhoneNumber: registerInfo.PhoneNumber, + HashPassword: registerInfo.Password, + CreatedDate: time.Now(), + UpdateDate: time.Now(), + VerifySentCount: 1, + } + + services.CheckAndWritePreuser(preUserData) + + //debug + if isValidData == true || validRegisterInfo == true { + return utils.WriteJSON(w, http.StatusOK, "USER HAVE VALID INFO FOR REGISTER ACCOUNT") + } + //debug } return nil diff --git a/rest-api/services/register.go b/rest-api/services/register.go index 556d2ac..4fe5e57 100644 --- a/rest-api/services/register.go +++ b/rest-api/services/register.go @@ -3,8 +3,8 @@ package services import ( "context" "encoding/base32" - "encoding/base64" "encoding/json" + "errors" "fmt" "github.com/go-playground/validator/v10" "github.com/pquerna/otp" @@ -15,10 +15,16 @@ import ( "linhdevtran99/rest-api/models" "linhdevtran99/rest-api/utils" "net/http" + "time" ) -// Check the user register field is valid or not -func CheckAndValidRegisterFiled(registerData *models.CreateUser, w http.ResponseWriter) bool { +type ResponseError struct { + Code int + Err error +} + +// CheckAndValidRegisterFiled Check the user register field is valid or not +func CheckAndValidRegisterFiled(registerData *models.CreateUser) (bool, *ResponseError) { _ = models.Validate.RegisterValidation("customPassword", models.PasswordValidator) errs := models.Validate.Struct(registerData) var errStack []string @@ -27,49 +33,45 @@ func CheckAndValidRegisterFiled(registerData *models.CreateUser, w http.Response switch err.Field() { case "Email": { - fmt.Println("Email không hợp lệ") + fmt.Println("Internal Log: Email không hợp lệ") errStack = append(errStack, "Email") } case "Username": { - fmt.Println("User không hợp lệ") + fmt.Println("Internal Log: User không hợp lệ") errStack = append(errStack, "User") } case "Password": { - fmt.Println("Password không hợp lệ") + fmt.Println("Internal Log: Password không hợp lệ") errStack = append(errStack, "Password") } case "ConfirmPassword": { - fmt.Println("Nhập lại mật khẩu sai") + fmt.Println("Internal Log: Nhập lại mật khẩu sai") errStack = append(errStack, "ConfirmPassword") } case "PhoneNumber": { - fmt.Println("SĐT không hợp lệ") + fmt.Println("Internal Log: SĐT không hợp lệ") errStack = append(errStack, "PhoneNumber") } } } //handle repose error - w.WriteHeader(http.StatusBadRequest) - err := json.NewEncoder(w).Encode(errStack) - - if err != nil { - fmt.Println("There is error in write reponse at checking info user") - } - return false + jsonData, _ := json.Marshal(errStack) + return false, &ResponseError{Code: http.StatusBadRequest, Err: errors.New(string(jsonData))} } - return true + return true, nil } -// Checking in db is user input same data in -func CheckAccountExist(mongoClient *mongo.Client, userName string, email string, w http.ResponseWriter) bool { +// CheckAccountExist Checking in db is user input same data in +func CheckAccountExist(mongoClient *mongo.Client, userName string, email string) (bool, *ResponseError) { //filter in mongodb var isValid bool + var errAPI *ResponseError filter := bson.D{ {"$or", bson.A{ @@ -78,27 +80,24 @@ func CheckAccountExist(mongoClient *mongo.Client, userName string, email string, }}, } - userData := mongoClient.Database("Totoday-shop").Collection("users") - - var result models.UsersMongo - err := userData.FindOne(context.TODO(), filter).Decode(&result) + _, err := utils.PreUserData.FindOne(context.TODO(), filter).Raw() if err != nil { isValid = true - } - if err == nil { - fmt.Println("Email or username already had register") - w.WriteHeader(http.StatusBadRequest) - err := json.NewEncoder(w).Encode("Your username or email had been register before") - if err != nil { - fmt.Println("There is error in write reponse at checking data user register") - } + errAPI = nil + } else { + fmt.Println("Internal Log: Email or username already had register") isValid = false + errAPI = &ResponseError{ + Code: http.StatusBadRequest, + Err: errors.New("your username or email had been register before"), + } } - return isValid + return isValid, errAPI } -// Generate HOtp for confirm infomation +// GeneratorOtp Generate HOtp for confirm information + func GeneratorOtp(userName string, email string, counter uint64, serect string) (bool, *models.OtpGenerate) { serectBase32 := base32.StdEncoding.EncodeToString([]byte(serect + userName + email)) @@ -109,19 +108,13 @@ func GeneratorOtp(userName string, email string, counter uint64, serect string) if err != nil { fmt.Println("Fail to create OTP") - return false, &models.OtpGenerate{ - PureOTP: "none", - HashOTP: "none", - } + return false, nil } hashed, err := bcrypt.GenerateFromPassword([]byte(passCode), 5) if err != nil { fmt.Println("Fail to create OTP") - return false, &models.OtpGenerate{ - PureOTP: "none", - HashOTP: "none", - } + return false, nil } return true, &models.OtpGenerate{ @@ -130,15 +123,43 @@ func GeneratorOtp(userName string, email string, counter uint64, serect string) } } -// Create a alternative verify link -func CreateLinkVerify(registerInfo *models.CreateUser, secrect string) string { +// CreateLinkVerify Create a alternative verify link +func CreateLinkVerify(registerInfo string, secrect string) string { + return "linh" + +} + +// check and write pre use into mongo db +func CheckAndWritePreuser(registerInfo *models.PreusersMongo) { + //checking + email := registerInfo.Email + filter := bson.D{{"email", email}} + _, err := utils.PreUserData.FindOne(context.Background(), filter).Raw() + if err != nil { + hashed, err := bcrypt.GenerateFromPassword([]byte(registerInfo.HashPassword), 10) + if err != nil { + fmt.Println("Fail to create OTP") + } + registerInfo.HashPassword = string(hashed) + _, err = utils.PreUserData.InsertOne(context.Background(), registerInfo) + if err != nil { + fmt.Println("Something Went Wrong") + } + } else { + update := bson.D{ + {"$inc", bson.D{ + {"verify_sent_count", 1}, + }}, + {"$set", bson.D{ + {"update_date", time.Now()}, + }, + }} + + _, err := utils.PreUserData.UpdateOne(context.Background(), filter, update) + if err != nil { + fmt.Println("Internal log: update document fail") + } + + } - data, _ := json.Marshal(registerInfo) - key := []byte(secrect) - - ciphertext, _ := utils.EncryptAES(data, key) - - cipherTextBase64 := base64.StdEncoding.EncodeToString(ciphertext) - - return cipherTextBase64 } diff --git a/utils/crypto.go b/utils/crypto.go index c2d8e7a..1ba2527 100644 --- a/utils/crypto.go +++ b/utils/crypto.go @@ -1,79 +1,83 @@ package utils import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "crypto/rand" "encoding/base64" "fmt" + "github.com/golang-jwt/jwt/v5" + "github.com/skip2/go-qrcode" + "net/http" + "strings" + "time" ) -func EncryptAES(data []byte, key []byte) ([]byte, error) { - block, err := aes.NewCipher(key) +//Mail + +type MyCustomClaims struct { + Email string `json:"email"` + jwt.RegisteredClaims +} + +func buildTokenLink(token string, w http.ResponseWriter) (string, string) { + tokenCustomTrim := strings.ReplaceAll(token, ".", "&") + emailVerifyLink := "http://www.totoday.com/?p=" + tokenCustomTrim + fmt.Println(emailVerifyLink) + png, err := qrcode.Encode(emailVerifyLink, qrcode.Low, 200) if err != nil { - return nil, err + fmt.Println("Internal log: error create qr ") + _ = WriteJSONInternalError(w, "error create QR code") } + base64Image := base64.StdEncoding.EncodeToString(png) + dataURL := "data:image/png;base64," + base64Image - // Generate a random IV (Initialization Vector) - iv := make([]byte, aes.BlockSize) - if _, err := rand.Read(iv); err != nil { - fmt.Println("error generating IV:", err) - } - - // Pad the data to a multiple of the block size - data = pkcs7Pad(data, aes.BlockSize) - - // Create a CBC mode cipher block - mode := cipher.NewCBCEncrypter(block, iv) - - // Encrypt the data - ciphertext := make([]byte, len(data)) - mode.CryptBlocks(ciphertext, data) - - // Prepend the IV to the ciphertext - ciphertext = append(iv, ciphertext...) - - return ciphertext, nil + return emailVerifyLink, dataURL } -func DecryptAES(base64Ciphertext string, key []byte) ([]byte, error) { - block, err := aes.NewCipher(key) +func EncryptAESMailLink(data string, key string, w http.ResponseWriter) (string, string) { + claims := MyCustomClaims{ + data, + jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), + IssuedAt: jwt.NewNumericDate(time.Now()), + NotBefore: jwt.NewNumericDate(time.Now()), + Issuer: "totodayShop", + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + token.Header["purpose"] = "Email_Verify" + + tokenString, err := token.SignedString([]byte(key)) if err != nil { - return nil, err + fmt.Println("Internal Log: Error signed token fail", err.Error()) + _ = WriteJSONInternalError(w, "Error signing token fail") } - // Decode base64 - ciphertext, err := base64.StdEncoding.DecodeString(base64Ciphertext) - if err != nil { - fmt.Println("error decoding base64:", err) - } - - // Extract the IV from the ciphertext - iv := ciphertext[:aes.BlockSize] - ciphertext = ciphertext[aes.BlockSize:] - - // Create a CBC mode cipher block - mode := cipher.NewCBCDecrypter(block, iv) - - // Decrypt the data - mode.CryptBlocks(ciphertext, ciphertext) - - // Remove padding - ciphertext = pkcs7Unpad(ciphertext) - - return ciphertext, nil + return buildTokenLink(tokenString, w) } -// pkcs7Pad pads the input to a multiple of blockSize using PKCS#7 padding -func pkcs7Pad(data []byte, blockSize int) []byte { - padding := blockSize - len(data)%blockSize - padText := bytes.Repeat([]byte{byte(padding)}, padding) - return append(data, padText...) -} +// +//func DecryptAESMailLink(data string, key string) string { +//} -// pkcs7Unpad removes PKCS#7 padding from the input -func pkcs7Unpad(data []byte) []byte { - padding := int(data[len(data)-1]) - return data[:len(data)-padding] -} +//OTP +//Write preuser to mongo db + +//func WriteToMongo(registerInfo *models.PreusersMongo) { +// +// +//} + +//Write otp to redis + +//func CheckAndWriteRedis(email string, username string, hashOTP string) { +// //checking does it valid or have in redis or not +// //var count int +// exists, err := Redis.Exists(context.Background(), "otp:nhocdl.poro1@gmail.com").Result() +// if err != nil { +// fmt.Println("something went wrong") +// } +// if exists == 0 { +// //count = 0 +// +// } +//} diff --git a/utils/mongodb.go b/utils/mongodb.go index d9412a0..1bccfea 100644 --- a/utils/mongodb.go +++ b/utils/mongodb.go @@ -11,6 +11,7 @@ import ( ) var MongoDB *mongo.Client +var PreUserData *mongo.Collection func InitMongoDriver(mongoUri string) *mongo.Client { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -30,6 +31,7 @@ func InitMongoDriver(mongoUri string) *mongo.Client { } MongoDB = client + PreUserData = MongoDB.Database("Totoday-shop").Collection("preusers") return client } diff --git a/utils/restapiType.go b/utils/restapiType.go index 70cbbf1..bd54d39 100644 --- a/utils/restapiType.go +++ b/utils/restapiType.go @@ -9,7 +9,21 @@ import ( func WriteJSON(w http.ResponseWriter, status int, v any) error { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) - return json.NewEncoder(w).Encode(v) + err := json.NewEncoder(w).Encode(v) + if err != nil { + return err + } + return nil +} + +func WriteJSONInternalError(w http.ResponseWriter, v any) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + err := json.NewEncoder(w).Encode(v) + if err != nil { + return err + } + return nil } type ApiError struct {