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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,9 @@ R2_ACCOUNT_ID=oui
R2_ACCESS_KEY_ID=access_key_id
R2_ACCESS_KEY_SECRET=access_key_secret
R2_BUCKET_NAME=bucket_name
R2_PUBLIC_DOMAIN=https://your-custom-domain.com
R2_PUBLIC_DOMAIN=https://your-custom-domain.com

# Whatsapp pour la vérification du numéro de téléphone
WHATSAPP_API_ACCESS_TOKEN=whatsapp_api_access_token
WHATSAPP_BUSINESS_ACCOUNT_ID=whatsapp_business_account_id
WHATSAPP_PHONE_NUMBER_ID=whatsapp_phone_number_id
15 changes: 15 additions & 0 deletions db/migrations/00019_add_phone_verif_whatsapp.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- +goose Up
-- +goose StatementBegin
ALTER TABLE newf
ADD COLUMN phone_number_verified BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN phone_number_verification_code VARCHAR(6),
ADD COLUMN phone_number_verification_code_expiration TIMESTAMP;
Comment on lines +3 to +6
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Constrain the verification code format to 6 digits.

Add a CHECK to prevent invalid data at the DB layer.

 ALTER TABLE newf
     ADD COLUMN phone_number_verified BOOLEAN NOT NULL DEFAULT FALSE,
     ADD COLUMN phone_number_verification_code VARCHAR(6),
-    ADD COLUMN phone_number_verification_code_expiration TIMESTAMP;
+    ADD COLUMN phone_number_verification_code_expiration TIMESTAMPTZ,
+    ADD CONSTRAINT chk_newf_phone_code_6_digits
+      CHECK (phone_number_verification_code IS NULL OR phone_number_verification_code ~ '^[0-9]{6}$');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ALTER TABLE newf
ADD COLUMN phone_number_verified BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN phone_number_verification_code VARCHAR(6),
ADD COLUMN phone_number_verification_code_expiration TIMESTAMP;
ALTER TABLE newf
ADD COLUMN phone_number_verified BOOLEAN NOT NULL DEFAULT FALSE,
ADD COLUMN phone_number_verification_code VARCHAR(6),
ADD COLUMN phone_number_verification_code_expiration TIMESTAMPTZ,
ADD CONSTRAINT chk_newf_phone_code_6_digits
CHECK (phone_number_verification_code IS NULL OR phone_number_verification_code ~ '^[0-9]{6}$');
🤖 Prompt for AI Agents
In db/migrations/00019_add_phone_verif_whatsapp.sql around lines 3 to 6, add a
CHECK constraint to ensure phone_number_verification_code, if present, is
exactly six digits; modify the table alteration to include a named constraint
(e.g., chk_newf_phone_ver_code_format) that enforces
phone_number_verification_code ~ '^[0-9]{6}$' OR phone_number_verification_code
IS NULL so non-numeric or wrong-length values are rejected at the DB layer.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use TIMESTAMPTZ for absolute expirations.

Store expirations with timezone to avoid DST/offset bugs.

-    ADD COLUMN phone_number_verification_code_expiration TIMESTAMP;
+    ADD COLUMN phone_number_verification_code_expiration TIMESTAMPTZ;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ADD COLUMN phone_number_verification_code_expiration TIMESTAMP;
ADD COLUMN phone_number_verification_code_expiration TIMESTAMPTZ;
🤖 Prompt for AI Agents
In db/migrations/00019_add_phone_verif_whatsapp.sql around line 6, the migration
adds phone_number_verification_code_expiration as TIMESTAMP but should use
TIMESTAMPTZ to store absolute expirations with timezone; update the column type
to TIMESTAMPTZ (or ALTER TABLE ... ALTER COLUMN ... TYPE TIMESTAMPTZ in a
corrective migration) so expiration values include timezone info and avoid
DST/offset bugs, and ensure any default/insert code uses timezone-aware
timestamps.

-- +goose StatementEnd

-- +goose Down
-- +goose StatementBegin
ALTER TABLE newf
DROP COLUMN phone_number_verified,
DROP COLUMN phone_number_verification_code,
DROP COLUMN phone_number_verification_code_expiration;
-- +goose StatementEnd
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ require (
github.com/pkg/errors v0.9.1
github.com/pressly/goose/v3 v3.24.2
github.com/robfig/cron/v3 v3.0.1
github.com/wapikit/wapi.go v0.4.2
golang.org/x/crypto v0.36.0
golang.org/x/text v0.23.0
google.golang.org/api v0.224.0
Expand Down Expand Up @@ -49,13 +50,20 @@ require (
github.com/aws/aws-sdk-go-v2/service/sts v1.33.21 // indirect
github.com/aws/smithy-go v1.22.2 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/gabriel-vasile/mimetype v1.4.6 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
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.23.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.5 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/labstack/echo/v4 v4.12.0 // indirect
github.com/labstack/gommon v0.4.2 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
Expand All @@ -66,6 +74,7 @@ require (
github.com/tinylib/msgp v1.2.5 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.57.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
Expand Down
20 changes: 20 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/gabriel-vasile/mimetype v1.4.6 h1:3+PzJTKLkvgjeTbts6msPJt4DixhT4YtFNf1gtGe3zc=
github.com/gabriel-vasile/mimetype v1.4.6/go.mod h1:JX1qVKqZd40hUPpAfiNTe0Sne7hdfKSbOqqmkq8GCXc=
github.com/getbrevo/brevo-go v1.1.3 h1:8TYrhhxbfAJLGArlPzCDKzbNfzvjIykBRhTDzLJqmyw=
github.com/getbrevo/brevo-go v1.1.3/go.mod h1:ExhytIoPxt/cOBl6ZEMeEZNLUKrWEYA5U3hM/8WP2bg=
github.com/getsentry/sentry-go v0.33.0 h1:YWyDii0KGVov3xOaamOnF0mjOrqSjBqwv48UEzn7QFg=
Expand All @@ -69,6 +71,14 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o=
github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
Expand All @@ -89,6 +99,12 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
Expand Down Expand Up @@ -131,8 +147,12 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.57.0 h1:Xw8SjWGEP/+wAAgyy5XTvgrWlOD1+TxbbvNADYCm1Tg=
github.com/valyala/fasthttp v1.57.0/go.mod h1:h6ZBaPRlzpZ6O3H5t2gEk1Qi33+TmLvfwgLLp0t9CpE=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/wapikit/wapi.go v0.4.2 h1:6jaa+zX4IESjQmIWi26aDgjF6/VWzGht2DvkV5a5LxU=
github.com/wapikit/wapi.go v0.4.2/go.mod h1:kd2cevBgVL/90JLbhi/dK14T+d2R2T/JnvBA2UcTcgs=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
Expand Down
253 changes: 247 additions & 6 deletions handlers/user/user_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ import (

// UserHandler handles user profile and related actions.
type UserHandler struct {
DB *sql.DB
NotifService *services.NotificationService // Inject if needed for notification handlers
DB *sql.DB
NotifService *services.NotificationService
PhoneVerificationService *services.PhoneVerificationService
}

// NewUserHandler creates a new UserHandler.
func NewUserHandler(db *sql.DB, notifService *services.NotificationService) *UserHandler {
func NewUserHandler(db *sql.DB, notifService *services.NotificationService, phoneVerificationService *services.PhoneVerificationService) *UserHandler {
return &UserHandler{
DB: db,
NotifService: notifService,
DB: db,
NotifService: notifService,
PhoneVerificationService: phoneVerificationService,
}
}

Expand Down Expand Up @@ -172,8 +174,55 @@ func (h *UserHandler) UpdateNewf(c *fiber.Ctx) error {
if req.LastName != "" {
updateFields["last_name"] = req.LastName
}
// cas spécial: si le numéro de téléphone change, on vérifie le numéro avec Whatsapp
if req.PhoneNumber != "" {
// Add validation for phone number format if needed
// il FAUT le préfixe international pour WA
if !strings.HasPrefix(req.PhoneNumber, "+") {
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid phone number"})
}

var currentPhoneNumber sql.NullString
phoneQuery := `SELECT phone_number FROM newf WHERE email = $1`
err := h.DB.QueryRow(phoneQuery, email).Scan(&currentPhoneNumber)
if err != nil {
utils.LogMessage(utils.LevelError, "Failed to fetch current phone number")
utils.LogLineKeyValue(utils.LevelError, "Error", err)
} else if !currentPhoneNumber.Valid || currentPhoneNumber.String != req.PhoneNumber {
utils.LogMessage(utils.LevelInfo, "Phone number changed, sending verification code")

Comment on lines +184 to +192
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Don’t proceed if fetching current phone number fails.

Continuing would let a phone number change slip through without triggering verification.

-	err := h.DB.QueryRow(phoneQuery, email).Scan(&currentPhoneNumber)
-	if err != nil {
-		utils.LogMessage(utils.LevelError, "Failed to fetch current phone number")
-		utils.LogLineKeyValue(utils.LevelError, "Error", err)
-	} else if !currentPhoneNumber.Valid || currentPhoneNumber.String != req.PhoneNumber {
+	err := h.DB.QueryRow(phoneQuery, email).Scan(&currentPhoneNumber)
+	if err != nil {
+		utils.LogMessage(utils.LevelError, "Failed to fetch current phone number")
+		utils.LogLineKeyValue(utils.LevelError, "Error", err)
+		utils.LogFooter()
+		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to verify phone number change"})
+	} else if !currentPhoneNumber.Valid || currentPhoneNumber.String != req.PhoneNumber {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var currentPhoneNumber sql.NullString
phoneQuery := `SELECT phone_number FROM newf WHERE email = $1`
err := h.DB.QueryRow(phoneQuery, email).Scan(&currentPhoneNumber)
if err != nil {
utils.LogMessage(utils.LevelError, "Failed to fetch current phone number")
utils.LogLineKeyValue(utils.LevelError, "Error", err)
} else if !currentPhoneNumber.Valid || currentPhoneNumber.String != req.PhoneNumber {
utils.LogMessage(utils.LevelInfo, "Phone number changed, sending verification code")
var currentPhoneNumber sql.NullString
phoneQuery := `SELECT phone_number FROM newf WHERE email = $1`
err := h.DB.QueryRow(phoneQuery, email).Scan(&currentPhoneNumber)
if err != nil {
utils.LogMessage(utils.LevelError, "Failed to fetch current phone number")
utils.LogLineKeyValue(utils.LevelError, "Error", err)
utils.LogFooter()
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to verify phone number change"})
} else if !currentPhoneNumber.Valid || currentPhoneNumber.String != req.PhoneNumber {
utils.LogMessage(utils.LevelInfo, "Phone number changed, sending verification code")
🤖 Prompt for AI Agents
In handlers/user/user_handlers.go around lines 184 to 192, the current code logs
an error when fetching the current phone number fails but continues execution,
which can allow an unverified phone change to proceed; modify the error branch
so that after logging the error you immediately return an appropriate HTTP error
response (e.g., 500 Internal Server Error) to the caller (or otherwise stop
further processing of the request) instead of falling through to the
phone-change logic, ensuring no verification is sent or phone updated when the
DB lookup fails.

// faut la langue pour wassap
languageCode, langErr := utils.GetLanguageCode(h.DB, email)
if langErr != nil {
utils.LogMessage(utils.LevelError, "Failed to get language code")
utils.LogLineKeyValue(utils.LevelError, "Error", langErr)
utils.LogFooter()
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to get language code"})
}

verificationCode, err := h.PhoneVerificationService.SendPhoneVerificationCode(req.PhoneNumber, languageCode)

if err != nil {
utils.LogMessage(utils.LevelError, "Failed to send phone verification code")
utils.LogLineKeyValue(utils.LevelError, "Error", err)
} else {
expirationTime := time.Now().Add(10 * time.Minute)
updateVerifQuery := `
UPDATE newf
SET phone_number_verification_code = $1,
phone_number_verification_code_expiration = $2,
phone_number_verified = false
WHERE email = $3
`
_, err := h.DB.Exec(updateVerifQuery, verificationCode, expirationTime, email)
if err != nil {
utils.LogMessage(utils.LevelError, "Failed to update verification code in database")
utils.LogLineKeyValue(utils.LevelError, "Error", err)
}
}
Comment on lines +202 to +221
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Make DB update mandatory when sending a new code; fail fast on errors.

If updating the verification fields fails, bail out; don’t continue to update the phone number later.

-			verificationCode, err := h.PhoneVerificationService.SendPhoneVerificationCode(req.PhoneNumber, languageCode)
-
-			if err != nil {
+			verificationCode, err := h.PhoneVerificationService.SendPhoneVerificationCode(req.PhoneNumber, languageCode)
+			if err != nil {
 				utils.LogMessage(utils.LevelError, "Failed to send phone verification code")
 				utils.LogLineKeyValue(utils.LevelError, "Error", err)
-			} else {
+				utils.LogFooter()
+				return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to send verification code"})
+			} else {
 				expirationTime := time.Now().Add(10 * time.Minute)
-				updateVerifQuery := `
-					UPDATE newf 
-					SET phone_number_verification_code = $1,
-					    phone_number_verification_code_expiration = $2,
-					    phone_number_verified = false
-					WHERE email = $3
-				`
-				_, err := h.DB.Exec(updateVerifQuery, verificationCode, expirationTime, email)
+				updateVerifQuery := `
+					UPDATE newf 
+					SET phone_number = $1,
+					    phone_number_verification_code = $2,
+					    phone_number_verification_code_expiration = $3,
+					    phone_number_verified = false
+					WHERE email = $4
+				`
+				_, err := h.DB.Exec(updateVerifQuery, req.PhoneNumber, verificationCode, expirationTime, email)
 				if err != nil {
 					utils.LogMessage(utils.LevelError, "Failed to update verification code in database")
 					utils.LogLineKeyValue(utils.LevelError, "Error", err)
+					utils.LogFooter()
+					return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to persist verification code"})
 				}
 			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
verificationCode, err := h.PhoneVerificationService.SendPhoneVerificationCode(req.PhoneNumber, languageCode)
if err != nil {
utils.LogMessage(utils.LevelError, "Failed to send phone verification code")
utils.LogLineKeyValue(utils.LevelError, "Error", err)
} else {
expirationTime := time.Now().Add(10 * time.Minute)
updateVerifQuery := `
UPDATE newf
SET phone_number_verification_code = $1,
phone_number_verification_code_expiration = $2,
phone_number_verified = false
WHERE email = $3
`
_, err := h.DB.Exec(updateVerifQuery, verificationCode, expirationTime, email)
if err != nil {
utils.LogMessage(utils.LevelError, "Failed to update verification code in database")
utils.LogLineKeyValue(utils.LevelError, "Error", err)
}
}
verificationCode, err := h.PhoneVerificationService.SendPhoneVerificationCode(req.PhoneNumber, languageCode)
if err != nil {
utils.LogMessage(utils.LevelError, "Failed to send phone verification code")
utils.LogLineKeyValue(utils.LevelError, "Error", err)
utils.LogFooter()
return c.Status(fiber.StatusInternalServerError).
JSON(fiber.Map{"error": "Failed to send verification code"})
} else {
expirationTime := time.Now().Add(10 * time.Minute)
updateVerifQuery := `
UPDATE newf
SET phone_number = $1,
phone_number_verification_code = $2,
phone_number_verification_code_expiration = $3,
phone_number_verified = false
WHERE email = $4
`
_, err := h.DB.Exec(
updateVerifQuery,
req.PhoneNumber,
verificationCode,
expirationTime,
email,
)
if err != nil {
utils.LogMessage(utils.LevelError, "Failed to update verification code in database")
utils.LogLineKeyValue(utils.LevelError, "Error", err)
utils.LogFooter()
return c.Status(fiber.StatusInternalServerError).
JSON(fiber.Map{"error": "Failed to persist verification code"})
}
}
🤖 Prompt for AI Agents
In handlers/user/user_handlers.go around lines 202 to 221, the code currently
sends a phone verification code but continues execution even if the subsequent
DB update fails; change this so the DB update is mandatory and the handler fails
fast on error: after calling h.DB.Exec to write phone_number_verification_code
and expiration, check the returned err and if non-nil log the error and
immediately return an appropriate error response (and do not proceed with any
further phone-number updates or success flows); ensure you set HTTP status and
body consistently for this failure path and avoid continuing execution after the
DB error.

}

// on update tout de suite (une fois qu'on est sûr que le code est parti),
// mais il ne faut PAS afficher ce numéro dans l'appli car pas encore vérifié
updateFields["phone_number"] = req.PhoneNumber
}
Comment on lines +224 to 227
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Don’t update phone number unless verification record was stored.

This line can leave the account with a new number marked as verified. It’s now handled in the single UPDATE above; remove this assignment.

-	updateFields["phone_number"] = req.PhoneNumber
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// on update tout de suite (une fois qu'on est sûr que le code est parti),
// mais il ne faut PAS afficher ce numéro dans l'appli car pas encore vérifié
updateFields["phone_number"] = req.PhoneNumber
}
// on update tout de suite (une fois qu'on est sûr que le code est parti),
// mais il ne faut PAS afficher ce numéro dans l'appli car pas encore vérifié
}
🤖 Prompt for AI Agents
In handlers/user/user_handlers.go around lines 224 to 227, the code assigns
updateFields["phone_number"] = req.PhoneNumber which can mark the account as
having a new verified number before a verification record is persisted; remove
this assignment so the phone number is not updated here and let the earlier
single UPDATE (already handling phone updates when appropriate) be the only
place that sets the phone number after verification record storage.

if req.GraduationYear != 0 {
Expand Down Expand Up @@ -269,6 +318,33 @@ func (h *UserHandler) UpdateNewf(c *fiber.Ctx) error {
utils.LogLineKeyValue(utils.LevelInfo, "Fields Updated", updateFields) // Log only keys for brevity/privacy
utils.LogFooter()

// Vérifier si une vérification de téléphone est nécessaire
var phoneVerificationRequired bool
var phoneNumber string
if req.PhoneNumber != "" {
// Vérifier si le numéro a changé et n'est pas encore vérifié
verifQuery := `
SELECT phone_number, phone_number_verified
FROM newf
WHERE email = $1
`
err := h.DB.QueryRow(verifQuery, email).Scan(&phoneNumber, &phoneVerificationRequired)
if err != nil {
utils.LogMessage(utils.LevelError, "Failed to check phone verification status")
} else {
phoneVerificationRequired = !phoneVerificationRequired
}
}

// Retourner une réponse différente si une vérification est nécessaire
if phoneVerificationRequired && req.PhoneNumber != "" {
return c.Status(fiber.StatusOK).JSON(fiber.Map{
"message": "Profile updated successfully. Phone number verification required.",
"phone_verification_required": true,
"phone_number": req.PhoneNumber,
})
}

return c.SendStatus(fiber.StatusOK)
}

Expand Down Expand Up @@ -587,3 +663,168 @@ func (h *UserHandler) SendNotification(c *fiber.Ctx) error {
// It needs to be implemented fully to handle parsing, target resolution, etc.
return h.NotifService.SendNotification(c)
}

// whatsapp
func (h *UserHandler) VerifyPhoneNumber(c *fiber.Ctx) error {
email := c.Locals("email").(string)
var req struct {
VerificationCode string `json:"verification_code"`
}

utils.LogHeader("📱 Verifying Phone Number (avec whatsapp)")
utils.LogLineKeyValue(utils.LevelInfo, "User", email)

if err := c.BodyParser(&req); err != nil {
utils.LogMessage(utils.LevelError, "Failed to parse request body")
utils.LogLineKeyValue(utils.LevelError, "Error", err)
utils.LogFooter()
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid request format"})
}

if req.VerificationCode == "" {
utils.LogMessage(utils.LevelWarn, "Missing verification code")
utils.LogFooter()
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Verification code is required"})
}

var storedCode, expirationTime string
var phoneNumber string
query := `
SELECT phone_number_verification_code,
phone_number_verification_code_expiration,
phone_number
FROM newf
WHERE email = $1
`

err := h.DB.QueryRow(query, email).Scan(&storedCode, &expirationTime, &phoneNumber)
if err != nil {
Comment on lines +690 to +701
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Scan expiration as time.Time to avoid RFC3339 parsing issues.

Postgres may not return RFC3339 when scanned into string. Scan into time.Time and format for the validator.

-	var storedCode, expirationTime string
+	var storedCode string
+	var expirationTime time.Time
@@
-	err := h.DB.QueryRow(query, email).Scan(&storedCode, &expirationTime, &phoneNumber)
+	err := h.DB.QueryRow(query, email).Scan(&storedCode, &expirationTime, &phoneNumber)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var storedCode, expirationTime string
var phoneNumber string
query := `
SELECT phone_number_verification_code,
phone_number_verification_code_expiration,
phone_number
FROM newf
WHERE email = $1
`
err := h.DB.QueryRow(query, email).Scan(&storedCode, &expirationTime, &phoneNumber)
if err != nil {
var storedCode string
var expirationTime time.Time
- var storedCode, expirationTime string
var phoneNumber string
query := `
SELECT phone_number_verification_code,
phone_number_verification_code_expiration,
phone_number
FROM newf
WHERE email = $1
`
err := h.DB.QueryRow(query, email).Scan(&storedCode, &expirationTime, &phoneNumber)
if err != nil {
🤖 Prompt for AI Agents
In handlers/user/user_handlers.go around lines 690 to 701, change the expiration
variable from string to time.Time, update the QueryRow.Scan call to
Scan(&storedCode, &expirationTime, &phoneNumber), and then pass a properly
formatted RFC3339 string to the validator by calling
expirationTime.UTC().Format(time.RFC3339) (or pass the time.Time directly if the
validator accepts it); ensure you import the time package if not already
imported.

if err == sql.ErrNoRows {
utils.LogMessage(utils.LevelWarn, "User not found???")
utils.LogFooter()
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}
utils.LogMessage(utils.LevelError, "Failed to fetch verification data")
utils.LogLineKeyValue(utils.LevelError, "Error", err)
utils.LogFooter()
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to fetch verification data"})
}

if !h.PhoneVerificationService.ValidateVerificationCode(storedCode, req.VerificationCode, expirationTime) {
utils.LogMessage(utils.LevelWarn, "Invalid or expired verification code")
utils.LogFooter()
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid or expired verification code"})
}
Comment on lines +713 to +717
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Pass RFC3339 string to validator (or refactor validator to accept time.Time).

Prevents false “expired” due to parse errors.

-	if !h.PhoneVerificationService.ValidateVerificationCode(storedCode, req.VerificationCode, expirationTime) {
+	if !h.PhoneVerificationService.ValidateVerificationCode(storedCode, req.VerificationCode, expirationTime.Format(time.RFC3339)) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if !h.PhoneVerificationService.ValidateVerificationCode(storedCode, req.VerificationCode, expirationTime) {
utils.LogMessage(utils.LevelWarn, "Invalid or expired verification code")
utils.LogFooter()
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid or expired verification code"})
}
if !h.PhoneVerificationService.ValidateVerificationCode(
storedCode, req.VerificationCode, expirationTime.Format(time.RFC3339),
) {
utils.LogMessage(utils.LevelWarn, "Invalid or expired verification code")
utils.LogFooter()
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid or expired verification code"})
}


// marquer comme verified
updateQuery := `
UPDATE newf
SET phone_number_verified = true,
phone_number_verification_code = NULL,
phone_number_verification_code_expiration = NULL
WHERE email = $1
`

_, err = h.DB.Exec(updateQuery, email)
if err != nil {
utils.LogMessage(utils.LevelError, "Failed to update phone verification status")
utils.LogLineKeyValue(utils.LevelError, "Error", err)
utils.LogFooter()
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to verify phone number"})
}

utils.LogMessage(utils.LevelInfo, "Phone number verified successfully")
utils.LogLineKeyValue(utils.LevelInfo, "Phone", phoneNumber)
utils.LogFooter()

return c.JSON(fiber.Map{
"message": "Phone number verified successfully",
"phone_number": phoneNumber,
"verified": true,
})
}

func (h *UserHandler) ResendPhoneVerificationCode(c *fiber.Ctx) error {
email := c.Locals("email").(string)

utils.LogHeader("📱 Resend Phone Verification Code")
utils.LogLineKeyValue(utils.LevelInfo, "User", email)

// Récupérer le numéro de téléphone et la langue de l'utilisateur, et vérifier s'il est pas déjà vérifié
var phoneNumber string
var verified bool
query := `
SELECT n.phone_number, n.phone_number_verified
FROM newf n
WHERE n.email = $1
`

err := h.DB.QueryRow(query, email).Scan(&phoneNumber, &verified)
if err != nil {
if err == sql.ErrNoRows {
utils.LogMessage(utils.LevelWarn, "User not found")
utils.LogFooter()
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}
utils.LogMessage(utils.LevelError, "Failed to fetch user data")
utils.LogLineKeyValue(utils.LevelError, "Error", err)
utils.LogFooter()
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to fetch user data"})
}

if verified {
utils.LogMessage(utils.LevelWarn, "Phone number already verified")
utils.LogFooter()
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Phone number already verified"})
}

if phoneNumber == "" {
utils.LogMessage(utils.LevelWarn, "No phone number found for user")
utils.LogFooter()
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "No phone number found"})
}

languageCode, langErr := utils.GetLanguageCode(h.DB, email)
if langErr != nil {
utils.LogMessage(utils.LevelError, "Failed to get language code")
utils.LogLineKeyValue(utils.LevelError, "Error", langErr)
utils.LogFooter()
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to get language code"})
}

// Envoyer un nouveau code de vérification
verificationCode, err := h.PhoneVerificationService.SendPhoneVerificationCode(phoneNumber, languageCode)
if err != nil {
utils.LogMessage(utils.LevelError, "Failed to send verification code")
utils.LogLineKeyValue(utils.LevelError, "Error", err)
utils.LogFooter()
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to send verification code"})
}

// Mettre à jour le code et l'expiration dans la base de données
expirationTime := time.Now().Add(10 * time.Minute)
updateQuery := `
UPDATE newf
SET phone_number_verification_code = $1,
phone_number_verification_code_expiration = $2,
phone_number_verified = false
WHERE email = $3
`

_, err = h.DB.Exec(updateQuery, verificationCode, expirationTime, email)
if err != nil {
utils.LogMessage(utils.LevelError, "Failed to update verification code in database")
utils.LogLineKeyValue(utils.LevelError, "Error", err)
utils.LogFooter()
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Failed to save verification code"})
}

utils.LogMessage(utils.LevelInfo, "Verification code resent successfully")
utils.LogLineKeyValue(utils.LevelInfo, "Phone", phoneNumber)
utils.LogFooter()

return c.JSON(fiber.Map{
"message": "Verification code sent successfully",
"phone_number": phoneNumber,
})
}
Loading