From 513927f48f9031e403f5344a1bdafc16b617c328 Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Wed, 14 Jan 2026 02:27:26 -0800 Subject: [PATCH] data seed and authorization fixes --- db/data/001_initial_seed_data.sql | 8 +- db/data/003_fix_autoincrement_desync.sql | 102 +++-- db/migrations/000001_yimaru.up.sql | 23 +- db/query/otp.sql | 8 +- db/query/user.sql | 184 +++++---- gen/db/models.go | 13 +- gen/db/otp.sql.go | 24 +- gen/db/user.sql.go | 379 ++++++++---------- internal/domain/otp.go | 2 +- internal/domain/user.go | 146 +++---- internal/ports/user.go | 22 +- internal/repository/auth.go | 10 +- internal/repository/otp.go | 24 +- internal/repository/user.go | 317 ++++++++------- internal/services/user/common.go | 17 +- internal/services/user/direct.go | 4 +- internal/services/user/register.go | 31 +- internal/services/user/reset.go | 8 +- internal/services/user/user.go | 55 +-- internal/web_server/handlers/admin.go | 46 +-- .../handlers/transaction_approver.go | 15 +- internal/web_server/handlers/user.go | 178 ++++---- internal/web_server/routes.go | 2 +- 23 files changed, 787 insertions(+), 831 deletions(-) diff --git a/db/data/001_initial_seed_data.sql b/db/data/001_initial_seed_data.sql index e23ef9e..5e4725f 100644 --- a/db/data/001_initial_seed_data.sql +++ b/db/data/001_initial_seed_data.sql @@ -5,7 +5,7 @@ INSERT INTO users ( id, first_name, last_name, - user_name, + -- user_name, email, phone_number, role, @@ -22,7 +22,7 @@ VALUES 10, 'Demo', 'Student', - 'demo_student', + -- 'demo_student', 'student10@yimaru.com', NULL, 'USER', @@ -38,7 +38,7 @@ VALUES 11, 'System', 'Admin', - 'sys_admin', + -- 'sys_admin', 'admin@yimaru.com', '0911001100', 'ADMIN', @@ -54,7 +54,7 @@ VALUES 12, 'Support', 'Agent', - 'support_agent', + -- 'support_agent', 'support@yimaru.com', '0911223344', 'SUPPORT', diff --git a/db/data/003_fix_autoincrement_desync.sql b/db/data/003_fix_autoincrement_desync.sql index c615073..33cfb3d 100644 --- a/db/data/003_fix_autoincrement_desync.sql +++ b/db/data/003_fix_autoincrement_desync.sql @@ -1,64 +1,80 @@ -- ====================================================== --- Reset sequences for LMS tables +-- Reset sequences for LMS tables (PostgreSQL) -- ====================================================== +-- users.id (BIGSERIAL) SELECT setval( pg_get_serial_sequence('users', 'id'), - COALESCE(MAX(id), 1) -) -FROM users; - + COALESCE((SELECT MAX(id) FROM users), 1), + true +); +-- assessment_questions.id (BIGSERIAL) SELECT setval( - pg_get_serial_sequence('courses', 'id'), - COALESCE(MAX(id), 1) -) -FROM courses; + pg_get_serial_sequence('assessment_questions', 'id'), + COALESCE((SELECT MAX(id) FROM assessment_questions), 1), + true +); +-- assessment_question_options.id (BIGSERIAL) SELECT setval( - pg_get_serial_sequence('course_modules', 'id'), - COALESCE(MAX(id), 1) -) -FROM course_modules; + pg_get_serial_sequence('assessment_question_options', 'id'), + COALESCE((SELECT MAX(id) FROM assessment_question_options), 1), + true +); +-- assessment_short_answers.id (BIGSERIAL) SELECT setval( - pg_get_serial_sequence('lessons', 'id'), - COALESCE(MAX(id), 1) -) -FROM lessons; + pg_get_serial_sequence('assessment_short_answers', 'id'), + COALESCE((SELECT MAX(id) FROM assessment_short_answers), 1), + true +); +-- assessment_attempts.id (BIGSERIAL) SELECT setval( - pg_get_serial_sequence('enrollments', 'id'), - COALESCE(MAX(id), 1) -) -FROM enrollments; + pg_get_serial_sequence('assessment_attempts', 'id'), + COALESCE((SELECT MAX(id) FROM assessment_attempts), 1), + true +); +-- assessment_attempt_questions.id (BIGSERIAL) SELECT setval( - pg_get_serial_sequence('assessments', 'id'), - COALESCE(MAX(id), 1) -) -FROM assessments; + pg_get_serial_sequence('assessment_attempt_questions', 'id'), + COALESCE((SELECT MAX(id) FROM assessment_attempt_questions), 1), + true +); +-- assessment_attempt_answers.id (BIGSERIAL) SELECT setval( - pg_get_serial_sequence('assessment_submissions', 'id'), - COALESCE(MAX(id), 1) -) -FROM assessment_submissions; + pg_get_serial_sequence('assessment_attempt_answers', 'id'), + COALESCE((SELECT MAX(id) FROM assessment_attempt_answers), 1), + true +); +-- refresh_tokens.id (BIGSERIAL) +SELECT setval( + pg_get_serial_sequence('refresh_tokens', 'id'), + COALESCE((SELECT MAX(id) FROM refresh_tokens), 1), + true +); + +-- otps.id (BIGSERIAL) +SELECT setval( + pg_get_serial_sequence('otps', 'id'), + COALESCE((SELECT MAX(id) FROM otps), 1), + true +); + +-- notifications.id (BIGSERIAL) SELECT setval( pg_get_serial_sequence('notifications', 'id'), - COALESCE(MAX(id), 1) -) -FROM notifications; + COALESCE((SELECT MAX(id) FROM notifications), 1), + true +); --- SELECT setval( --- pg_get_serial_sequence('referral_codes', 'id'), --- COALESCE(MAX(id), 1) --- ) --- FROM referral_codes; - --- SELECT setval( --- pg_get_serial_sequence('user_referrals', 'id'), --- COALESCE(MAX(id), 1) --- ) --- FROM user_referrals; +-- reported_issues.id (BIGSERIAL) +SELECT setval( + pg_get_serial_sequence('reported_issues', 'id'), + COALESCE((SELECT MAX(id) FROM reported_issues), 1), + true +); diff --git a/db/migrations/000001_yimaru.up.sql b/db/migrations/000001_yimaru.up.sql index 61f485d..7c07fa7 100644 --- a/db/migrations/000001_yimaru.up.sql +++ b/db/migrations/000001_yimaru.up.sql @@ -1,10 +1,13 @@ CREATE TABLE IF NOT EXISTS users ( id BIGSERIAL PRIMARY KEY, - first_name VARCHAR(255) NOT NULL, - last_name VARCHAR(255) NOT NULL, - user_name VARCHAR(100) NOT NULL, - email VARCHAR(255) UNIQUE, - phone_number VARCHAR(20) UNIQUE, + first_name VARCHAR(255), + last_name VARCHAR(255), + + gender VARCHAR(255), + birth_day DATE, + + email VARCHAR(255), + phone_number VARCHAR(20), role VARCHAR(50) NOT NULL, -- SUPER_ADMIN, INSTRUCTOR, STUDENT, SUPPORT password BYTEA NOT NULL, @@ -19,21 +22,23 @@ CREATE TABLE IF NOT EXISTS users ( learning_goal TEXT, language_goal TEXT, language_challange TEXT, - favoutite_topic TEXT, + favourite_topic TEXT, initial_assessment_completed BOOLEAN NOT NULL DEFAULT FALSE, email_verified BOOLEAN NOT NULL DEFAULT FALSE, phone_verified BOOLEAN NOT NULL DEFAULT FALSE, status VARCHAR(50) NOT NULL, -- PENDING, ACTIVE, SUSPENDED, DEACTIVATED last_login TIMESTAMPTZ, - profile_completed BOOLEAN NOT NULL DEFAULT FALSE, + profile_completed BOOLEAN, profile_picture_url TEXT, preferred_language VARCHAR(50), created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ, - CHECK (email IS NOT NULL OR phone_number IS NOT NULL) + -- Enforce: at least one contact method must be provided + CONSTRAINT users_email_or_phone_required + CHECK (email IS NOT NULL OR phone_number IS NOT NULL) ); CREATE TABLE IF NOT EXISTS assessment_questions ( @@ -167,7 +172,7 @@ CREATE TABLE refresh_tokens ( CREATE TABLE otps ( id BIGSERIAL PRIMARY KEY, - user_name VARCHAR(100) NOT NULL, + user_id BIGSERIAL NOT NULL, sent_to VARCHAR(255) NOT NULL, medium VARCHAR(50) NOT NULL, -- email, sms otp_for VARCHAR(50) NOT NULL, -- register, reset diff --git a/db/query/otp.sql b/db/query/otp.sql index 2c3e906..93274c4 100644 --- a/db/query/otp.sql +++ b/db/query/otp.sql @@ -5,11 +5,11 @@ SET used = FALSE, used_at = NULL, expires_at = $3 -WHERE user_name = $1; +WHERE user_id = $1; -- name: CreateOtp :exec INSERT INTO otps ( - user_name, + user_id, sent_to, medium, otp_for, @@ -19,9 +19,9 @@ INSERT INTO otps ( VALUES ($1, $2, $3, $4, $5, $6); -- name: GetOtp :one -SELECT id, user_name, sent_to, medium, otp_for, otp, used, used_at, created_at, expires_at +SELECT id, user_id, sent_to, medium, otp_for, otp, used, used_at, created_at, expires_at FROM otps -WHERE user_name = $1 +WHERE user_id = $1 ORDER BY created_at DESC LIMIT 1; -- name: MarkOtpAsUsed :exec diff --git a/db/query/user.sql b/db/query/user.sql index 515b353..3c56c2e 100644 --- a/db/query/user.sql +++ b/db/query/user.sql @@ -2,7 +2,7 @@ SELECT CASE WHEN status = 'PENDING' THEN true ELSE false END AS is_pending FROM users -WHERE user_name = $1 +WHERE id = $1 LIMIT 1; -- name: IsProfileCompleted :one @@ -16,14 +16,15 @@ LIMIT 1; SELECT CASE WHEN COUNT(*) = 0 THEN true ELSE false END AS is_unique FROM users -WHERE user_name = $1; +WHERE id = $1; -- name: CreateUser :one INSERT INTO users ( first_name, last_name, - user_name, + gender, + birth_day, email, phone_number, role, @@ -38,7 +39,7 @@ INSERT INTO users ( learning_goal, language_goal, language_challange, - favoutite_topic, + favourite_topic, initial_assessment_completed, email_verified, @@ -52,37 +53,39 @@ INSERT INTO users ( VALUES ( $1, -- first_name $2, -- last_name - $3, -- user_name - $4, -- email - $5, -- phone_number - $6, -- role - $7, -- password - $8, -- age - $9, -- education_level - $10, -- country - $11, -- region + $3, -- gender + $4, -- birth_day + $5, -- email + $6, -- phone_number + $7, -- role + $8, -- password + $9, -- age + $10, -- education_level + $11, -- country + $12, -- region - $12, -- nick_name - $13, -- occupation - $14, -- learning_goal - $15, -- language_goal - $16, -- language_challange - $17, -- favoutite_topic + $13, -- nick_name + $14, -- occupation + $15, -- learning_goal + $16, -- language_goal + $17, -- language_challange + $18, -- favourite_topic - $18, -- initial_assessment_completed - $19, -- email_verified - $20, -- phone_verified - $21, -- status - $22, -- profile_completed - $23, -- profile_picture_url - $24, -- preferred_language + $19, -- initial_assessment_completed + $20, -- email_verified + $21, -- phone_verified + $22, -- status + $23, -- profile_completed + $24, -- profile_picture_url + $25, -- preferred_language CURRENT_TIMESTAMP ) RETURNING id, first_name, last_name, - user_name, + gender, + birth_day, email, phone_number, role, @@ -96,7 +99,7 @@ RETURNING learning_goal, language_goal, language_challange, - favoutite_topic, + favourite_topic, initial_assessment_completed, email_verified, @@ -119,7 +122,8 @@ SELECT id, first_name, last_name, - user_name, + gender, + birth_day, email, phone_number, role, @@ -132,7 +136,7 @@ SELECT learning_goal, language_goal, language_challange, - favoutite_topic, + favourite_topic, initial_assessment_completed, profile_picture_url, preferred_language, @@ -163,7 +167,8 @@ SELECT id, first_name, last_name, - user_name, + gender, + birth_day, email, phone_number, role, @@ -177,7 +182,7 @@ SELECT learning_goal, language_goal, language_challange, - favoutite_topic, + favourite_topic, initial_assessment_completed, profile_picture_url, @@ -205,27 +210,32 @@ UPDATE users SET first_name = COALESCE($1, first_name), last_name = COALESCE($2, last_name), - user_name = COALESCE($3, user_name), - knowledge_level = COALESCE($4, knowledge_level), - age = COALESCE($5, age), - education_level = COALESCE($6, education_level), - country = COALESCE($7, country), - region = COALESCE($8, region), - nick_name = COALESCE($9, nick_name), - occupation = COALESCE($10, occupation), - learning_goal = COALESCE($11, learning_goal), - language_goal = COALESCE($12, language_goal), - language_challange = COALESCE($13, language_challange), - favoutite_topic = COALESCE($14, favoutite_topic), - initial_assessment_completed = COALESCE($15, initial_assessment_completed), - email_verified = COALESCE($16, email_verified), - phone_verified = COALESCE($17, phone_verified), - status = COALESCE($18, status), - profile_completed = COALESCE($19, profile_completed), - profile_picture_url = COALESCE($20, profile_picture_url), - preferred_language = COALESCE($21, preferred_language), + + -- email = COALESCE($3, email), + -- phone_number = COALESCE($4, phone_number), + + knowledge_level = COALESCE($3, knowledge_level), + age = COALESCE($4, age), + education_level = COALESCE($5, education_level), + country = COALESCE($6, country), + region = COALESCE($7, region), + nick_name = COALESCE($8, nick_name), + occupation = COALESCE($9, occupation), + learning_goal = COALESCE($10, learning_goal), + language_goal = COALESCE($11, language_goal), + language_challange = COALESCE($12, language_challange), + favourite_topic = COALESCE($13, favourite_topic), + initial_assessment_completed = COALESCE($14, initial_assessment_completed), + -- email_verified = COALESCE($15, email_verified), + -- phone_verified = COALESCE($16, phone_verified), + -- status = COALESCE($19, status), + profile_completed = COALESCE($15, profile_completed), + profile_picture_url = COALESCE($16, profile_picture_url), + preferred_language = COALESCE($17, preferred_language), + gender = COALESCE($18, gender), + birth_day = COALESCE($19, gender), updated_at = CURRENT_TIMESTAMP -WHERE id = $22; +WHERE id = $20; -- name: DeleteUser :exec DELETE FROM users @@ -240,47 +250,47 @@ SELECT SELECT 1 FROM users u2 WHERE u2.email = $2 ) AS email_exists; --- name: GetUserByUserName :one -SELECT - id, - first_name, - last_name, - user_name, - email, - phone_number, - role, - password, - age, - education_level, - country, - region, +-- -- name: GetUserByUserName :one +-- SELECT +-- id, +-- first_name, +-- last_name, +-- email, +-- phone_number, +-- role, +-- password, +-- age, +-- education_level, +-- country, +-- region, - nick_name, - occupation, - learning_goal, - language_goal, - language_challange, - favoutite_topic, +-- nick_name, +-- occupation, +-- learning_goal, +-- language_goal, +-- language_challange, +-- favourite_topic, - email_verified, - phone_verified, - status, - profile_completed, - last_login, - profile_picture_url, - preferred_language, - created_at, - updated_at -FROM users -WHERE user_name = $1 AND $1 IS NOT NULL -LIMIT 1; +-- email_verified, +-- phone_verified, +-- status, +-- profile_completed, +-- last_login, +-- profile_picture_url, +-- preferred_language, +-- created_at, +-- updated_at +-- FROM users +-- WHERE user_name = $1 AND $1 IS NOT NULL +-- LIMIT 1; -- name: GetUserByEmailPhone :one SELECT id, first_name, last_name, - user_name, + gender, + birth_day, email, phone_number, role, @@ -295,7 +305,7 @@ SELECT learning_goal, language_goal, language_challange, - favoutite_topic, + favourite_topic, email_verified, phone_verified, @@ -316,7 +326,7 @@ UPDATE users SET password = $1, updated_at = CURRENT_TIMESTAMP -WHERE user_name = $2; +WHERE id = $2; -- name: UpdateUserStatus :exec UPDATE users diff --git a/gen/db/models.go b/gen/db/models.go index 45e6e25..9694221 100644 --- a/gen/db/models.go +++ b/gen/db/models.go @@ -146,7 +146,7 @@ type Notification struct { type Otp struct { ID int64 `json:"id"` - UserName string `json:"user_name"` + UserID int64 `json:"user_id"` SentTo string `json:"sent_to"` Medium string `json:"medium"` OtpFor string `json:"otp_for"` @@ -213,9 +213,10 @@ type ReportedIssue struct { type User struct { ID int64 `json:"id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - UserName string `json:"user_name"` + FirstName pgtype.Text `json:"first_name"` + LastName pgtype.Text `json:"last_name"` + Gender pgtype.Text `json:"gender"` + BirthDay pgtype.Date `json:"birth_day"` Email pgtype.Text `json:"email"` PhoneNumber pgtype.Text `json:"phone_number"` Role string `json:"role"` @@ -230,13 +231,13 @@ type User struct { LearningGoal pgtype.Text `json:"learning_goal"` LanguageGoal pgtype.Text `json:"language_goal"` LanguageChallange pgtype.Text `json:"language_challange"` - FavoutiteTopic pgtype.Text `json:"favoutite_topic"` + FavouriteTopic pgtype.Text `json:"favourite_topic"` InitialAssessmentCompleted bool `json:"initial_assessment_completed"` EmailVerified bool `json:"email_verified"` PhoneVerified bool `json:"phone_verified"` Status string `json:"status"` LastLogin pgtype.Timestamptz `json:"last_login"` - ProfileCompleted bool `json:"profile_completed"` + ProfileCompleted pgtype.Bool `json:"profile_completed"` ProfilePictureUrl pgtype.Text `json:"profile_picture_url"` PreferredLanguage pgtype.Text `json:"preferred_language"` CreatedAt pgtype.Timestamptz `json:"created_at"` diff --git a/gen/db/otp.sql.go b/gen/db/otp.sql.go index 7b0d5f4..1b55f0c 100644 --- a/gen/db/otp.sql.go +++ b/gen/db/otp.sql.go @@ -13,7 +13,7 @@ import ( const CreateOtp = `-- name: CreateOtp :exec INSERT INTO otps ( - user_name, + user_id, sent_to, medium, otp_for, @@ -24,7 +24,7 @@ VALUES ($1, $2, $3, $4, $5, $6) ` type CreateOtpParams struct { - UserName string `json:"user_name"` + UserID int64 `json:"user_id"` SentTo string `json:"sent_to"` Medium string `json:"medium"` OtpFor string `json:"otp_for"` @@ -34,7 +34,7 @@ type CreateOtpParams struct { func (q *Queries) CreateOtp(ctx context.Context, arg CreateOtpParams) error { _, err := q.db.Exec(ctx, CreateOtp, - arg.UserName, + arg.UserID, arg.SentTo, arg.Medium, arg.OtpFor, @@ -45,15 +45,15 @@ func (q *Queries) CreateOtp(ctx context.Context, arg CreateOtpParams) error { } const GetOtp = `-- name: GetOtp :one -SELECT id, user_name, sent_to, medium, otp_for, otp, used, used_at, created_at, expires_at +SELECT id, user_id, sent_to, medium, otp_for, otp, used, used_at, created_at, expires_at FROM otps -WHERE user_name = $1 +WHERE user_id = $1 ORDER BY created_at DESC LIMIT 1 ` type GetOtpRow struct { ID int64 `json:"id"` - UserName string `json:"user_name"` + UserID int64 `json:"user_id"` SentTo string `json:"sent_to"` Medium string `json:"medium"` OtpFor string `json:"otp_for"` @@ -64,12 +64,12 @@ type GetOtpRow struct { ExpiresAt pgtype.Timestamptz `json:"expires_at"` } -func (q *Queries) GetOtp(ctx context.Context, userName string) (GetOtpRow, error) { - row := q.db.QueryRow(ctx, GetOtp, userName) +func (q *Queries) GetOtp(ctx context.Context, userID int64) (GetOtpRow, error) { + row := q.db.QueryRow(ctx, GetOtp, userID) var i GetOtpRow err := row.Scan( &i.ID, - &i.UserName, + &i.UserID, &i.SentTo, &i.Medium, &i.OtpFor, @@ -105,16 +105,16 @@ SET used = FALSE, used_at = NULL, expires_at = $3 -WHERE user_name = $1 +WHERE user_id = $1 ` type UpdateExpiredOtpParams struct { - UserName string `json:"user_name"` + UserID int64 `json:"user_id"` Otp string `json:"otp"` ExpiresAt pgtype.Timestamptz `json:"expires_at"` } func (q *Queries) UpdateExpiredOtp(ctx context.Context, arg UpdateExpiredOtpParams) error { - _, err := q.db.Exec(ctx, UpdateExpiredOtp, arg.UserName, arg.Otp, arg.ExpiresAt) + _, err := q.db.Exec(ctx, UpdateExpiredOtp, arg.UserID, arg.Otp, arg.ExpiresAt) return err } diff --git a/gen/db/user.sql.go b/gen/db/user.sql.go index 0c45308..afc5087 100644 --- a/gen/db/user.sql.go +++ b/gen/db/user.sql.go @@ -42,7 +42,8 @@ const CreateUser = `-- name: CreateUser :one INSERT INTO users ( first_name, last_name, - user_name, + gender, + birth_day, email, phone_number, role, @@ -57,7 +58,7 @@ INSERT INTO users ( learning_goal, language_goal, language_challange, - favoutite_topic, + favourite_topic, initial_assessment_completed, email_verified, @@ -71,37 +72,39 @@ INSERT INTO users ( VALUES ( $1, -- first_name $2, -- last_name - $3, -- user_name - $4, -- email - $5, -- phone_number - $6, -- role - $7, -- password - $8, -- age - $9, -- education_level - $10, -- country - $11, -- region + $3, -- gender + $4, -- birth_day + $5, -- email + $6, -- phone_number + $7, -- role + $8, -- password + $9, -- age + $10, -- education_level + $11, -- country + $12, -- region - $12, -- nick_name - $13, -- occupation - $14, -- learning_goal - $15, -- language_goal - $16, -- language_challange - $17, -- favoutite_topic + $13, -- nick_name + $14, -- occupation + $15, -- learning_goal + $16, -- language_goal + $17, -- language_challange + $18, -- favourite_topic - $18, -- initial_assessment_completed - $19, -- email_verified - $20, -- phone_verified - $21, -- status - $22, -- profile_completed - $23, -- profile_picture_url - $24, -- preferred_language + $19, -- initial_assessment_completed + $20, -- email_verified + $21, -- phone_verified + $22, -- status + $23, -- profile_completed + $24, -- profile_picture_url + $25, -- preferred_language CURRENT_TIMESTAMP ) RETURNING id, first_name, last_name, - user_name, + gender, + birth_day, email, phone_number, role, @@ -115,7 +118,7 @@ RETURNING learning_goal, language_goal, language_challange, - favoutite_topic, + favourite_topic, initial_assessment_completed, email_verified, @@ -129,9 +132,10 @@ RETURNING ` type CreateUserParams struct { - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - UserName string `json:"user_name"` + FirstName pgtype.Text `json:"first_name"` + LastName pgtype.Text `json:"last_name"` + Gender pgtype.Text `json:"gender"` + BirthDay pgtype.Date `json:"birth_day"` Email pgtype.Text `json:"email"` PhoneNumber pgtype.Text `json:"phone_number"` Role string `json:"role"` @@ -145,21 +149,22 @@ type CreateUserParams struct { LearningGoal pgtype.Text `json:"learning_goal"` LanguageGoal pgtype.Text `json:"language_goal"` LanguageChallange pgtype.Text `json:"language_challange"` - FavoutiteTopic pgtype.Text `json:"favoutite_topic"` + FavouriteTopic pgtype.Text `json:"favourite_topic"` InitialAssessmentCompleted bool `json:"initial_assessment_completed"` EmailVerified bool `json:"email_verified"` PhoneVerified bool `json:"phone_verified"` Status string `json:"status"` - ProfileCompleted bool `json:"profile_completed"` + ProfileCompleted pgtype.Bool `json:"profile_completed"` ProfilePictureUrl pgtype.Text `json:"profile_picture_url"` PreferredLanguage pgtype.Text `json:"preferred_language"` } type CreateUserRow struct { ID int64 `json:"id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - UserName string `json:"user_name"` + FirstName pgtype.Text `json:"first_name"` + LastName pgtype.Text `json:"last_name"` + Gender pgtype.Text `json:"gender"` + BirthDay pgtype.Date `json:"birth_day"` Email pgtype.Text `json:"email"` PhoneNumber pgtype.Text `json:"phone_number"` Role string `json:"role"` @@ -172,12 +177,12 @@ type CreateUserRow struct { LearningGoal pgtype.Text `json:"learning_goal"` LanguageGoal pgtype.Text `json:"language_goal"` LanguageChallange pgtype.Text `json:"language_challange"` - FavoutiteTopic pgtype.Text `json:"favoutite_topic"` + FavouriteTopic pgtype.Text `json:"favourite_topic"` InitialAssessmentCompleted bool `json:"initial_assessment_completed"` EmailVerified bool `json:"email_verified"` PhoneVerified bool `json:"phone_verified"` Status string `json:"status"` - ProfileCompleted bool `json:"profile_completed"` + ProfileCompleted pgtype.Bool `json:"profile_completed"` ProfilePictureUrl pgtype.Text `json:"profile_picture_url"` PreferredLanguage pgtype.Text `json:"preferred_language"` CreatedAt pgtype.Timestamptz `json:"created_at"` @@ -188,7 +193,8 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateU row := q.db.QueryRow(ctx, CreateUser, arg.FirstName, arg.LastName, - arg.UserName, + arg.Gender, + arg.BirthDay, arg.Email, arg.PhoneNumber, arg.Role, @@ -202,7 +208,7 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateU arg.LearningGoal, arg.LanguageGoal, arg.LanguageChallange, - arg.FavoutiteTopic, + arg.FavouriteTopic, arg.InitialAssessmentCompleted, arg.EmailVerified, arg.PhoneVerified, @@ -216,7 +222,8 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateU &i.ID, &i.FirstName, &i.LastName, - &i.UserName, + &i.Gender, + &i.BirthDay, &i.Email, &i.PhoneNumber, &i.Role, @@ -229,7 +236,7 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateU &i.LearningGoal, &i.LanguageGoal, &i.LanguageChallange, - &i.FavoutiteTopic, + &i.FavouriteTopic, &i.InitialAssessmentCompleted, &i.EmailVerified, &i.PhoneVerified, @@ -259,7 +266,8 @@ SELECT id, first_name, last_name, - user_name, + gender, + birth_day, email, phone_number, role, @@ -272,7 +280,7 @@ SELECT learning_goal, language_goal, language_challange, - favoutite_topic, + favourite_topic, initial_assessment_completed, profile_picture_url, preferred_language, @@ -306,9 +314,10 @@ type GetAllUsersParams struct { type GetAllUsersRow struct { TotalCount int64 `json:"total_count"` ID int64 `json:"id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - UserName string `json:"user_name"` + FirstName pgtype.Text `json:"first_name"` + LastName pgtype.Text `json:"last_name"` + Gender pgtype.Text `json:"gender"` + BirthDay pgtype.Date `json:"birth_day"` Email pgtype.Text `json:"email"` PhoneNumber pgtype.Text `json:"phone_number"` Role string `json:"role"` @@ -321,14 +330,14 @@ type GetAllUsersRow struct { LearningGoal pgtype.Text `json:"learning_goal"` LanguageGoal pgtype.Text `json:"language_goal"` LanguageChallange pgtype.Text `json:"language_challange"` - FavoutiteTopic pgtype.Text `json:"favoutite_topic"` + FavouriteTopic pgtype.Text `json:"favourite_topic"` InitialAssessmentCompleted bool `json:"initial_assessment_completed"` ProfilePictureUrl pgtype.Text `json:"profile_picture_url"` PreferredLanguage pgtype.Text `json:"preferred_language"` EmailVerified bool `json:"email_verified"` PhoneVerified bool `json:"phone_verified"` Status string `json:"status"` - ProfileCompleted bool `json:"profile_completed"` + ProfileCompleted pgtype.Bool `json:"profile_completed"` CreatedAt pgtype.Timestamptz `json:"created_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"` } @@ -354,7 +363,8 @@ func (q *Queries) GetAllUsers(ctx context.Context, arg GetAllUsersParams) ([]Get &i.ID, &i.FirstName, &i.LastName, - &i.UserName, + &i.Gender, + &i.BirthDay, &i.Email, &i.PhoneNumber, &i.Role, @@ -367,7 +377,7 @@ func (q *Queries) GetAllUsers(ctx context.Context, arg GetAllUsersParams) ([]Get &i.LearningGoal, &i.LanguageGoal, &i.LanguageChallange, - &i.FavoutiteTopic, + &i.FavouriteTopic, &i.InitialAssessmentCompleted, &i.ProfilePictureUrl, &i.PreferredLanguage, @@ -402,11 +412,15 @@ func (q *Queries) GetTotalUsers(ctx context.Context, role string) (int64, error) } const GetUserByEmailPhone = `-- name: GetUserByEmailPhone :one + + + SELECT id, first_name, last_name, - user_name, + gender, + birth_day, email, phone_number, role, @@ -421,7 +435,7 @@ SELECT learning_goal, language_goal, language_challange, - favoutite_topic, + favourite_topic, email_verified, phone_verified, @@ -445,9 +459,10 @@ type GetUserByEmailPhoneParams struct { type GetUserByEmailPhoneRow struct { ID int64 `json:"id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - UserName string `json:"user_name"` + FirstName pgtype.Text `json:"first_name"` + LastName pgtype.Text `json:"last_name"` + Gender pgtype.Text `json:"gender"` + BirthDay pgtype.Date `json:"birth_day"` Email pgtype.Text `json:"email"` PhoneNumber pgtype.Text `json:"phone_number"` Role string `json:"role"` @@ -461,11 +476,11 @@ type GetUserByEmailPhoneRow struct { LearningGoal pgtype.Text `json:"learning_goal"` LanguageGoal pgtype.Text `json:"language_goal"` LanguageChallange pgtype.Text `json:"language_challange"` - FavoutiteTopic pgtype.Text `json:"favoutite_topic"` + FavouriteTopic pgtype.Text `json:"favourite_topic"` EmailVerified bool `json:"email_verified"` PhoneVerified bool `json:"phone_verified"` Status string `json:"status"` - ProfileCompleted bool `json:"profile_completed"` + ProfileCompleted pgtype.Bool `json:"profile_completed"` LastLogin pgtype.Timestamptz `json:"last_login"` ProfilePictureUrl pgtype.Text `json:"profile_picture_url"` PreferredLanguage pgtype.Text `json:"preferred_language"` @@ -473,6 +488,39 @@ type GetUserByEmailPhoneRow struct { UpdatedAt pgtype.Timestamptz `json:"updated_at"` } +// -- name: GetUserByUserName :one +// SELECT +// +// id, +// first_name, +// last_name, +// email, +// phone_number, +// role, +// password, +// age, +// education_level, +// country, +// region, +// nick_name, +// occupation, +// learning_goal, +// language_goal, +// language_challange, +// favourite_topic, +// email_verified, +// phone_verified, +// status, +// profile_completed, +// last_login, +// profile_picture_url, +// preferred_language, +// created_at, +// updated_at +// +// FROM users +// WHERE user_name = $1 AND $1 IS NOT NULL +// LIMIT 1; func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPhoneParams) (GetUserByEmailPhoneRow, error) { row := q.db.QueryRow(ctx, GetUserByEmailPhone, arg.Email, arg.PhoneNumber) var i GetUserByEmailPhoneRow @@ -480,7 +528,8 @@ func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPho &i.ID, &i.FirstName, &i.LastName, - &i.UserName, + &i.Gender, + &i.BirthDay, &i.Email, &i.PhoneNumber, &i.Role, @@ -494,7 +543,7 @@ func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPho &i.LearningGoal, &i.LanguageGoal, &i.LanguageChallange, - &i.FavoutiteTopic, + &i.FavouriteTopic, &i.EmailVerified, &i.PhoneVerified, &i.Status, @@ -509,7 +558,7 @@ func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPho } const GetUserByID = `-- name: GetUserByID :one -SELECT id, first_name, last_name, user_name, email, phone_number, role, password, age, education_level, country, region, knowledge_level, nick_name, occupation, learning_goal, language_goal, language_challange, favoutite_topic, initial_assessment_completed, email_verified, phone_verified, status, last_login, profile_completed, profile_picture_url, preferred_language, created_at, updated_at +SELECT id, first_name, last_name, gender, birth_day, email, phone_number, role, password, age, education_level, country, region, knowledge_level, nick_name, occupation, learning_goal, language_goal, language_challange, favourite_topic, initial_assessment_completed, email_verified, phone_verified, status, last_login, profile_completed, profile_picture_url, preferred_language, created_at, updated_at FROM users WHERE id = $1 ` @@ -521,7 +570,8 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) { &i.ID, &i.FirstName, &i.LastName, - &i.UserName, + &i.Gender, + &i.BirthDay, &i.Email, &i.PhoneNumber, &i.Role, @@ -536,7 +586,7 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) { &i.LearningGoal, &i.LanguageGoal, &i.LanguageChallange, - &i.FavoutiteTopic, + &i.FavouriteTopic, &i.InitialAssessmentCompleted, &i.EmailVerified, &i.PhoneVerified, @@ -551,107 +601,6 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) { return i, err } -const GetUserByUserName = `-- name: GetUserByUserName :one -SELECT - id, - first_name, - last_name, - user_name, - email, - phone_number, - role, - password, - age, - education_level, - country, - region, - - nick_name, - occupation, - learning_goal, - language_goal, - language_challange, - favoutite_topic, - - email_verified, - phone_verified, - status, - profile_completed, - last_login, - profile_picture_url, - preferred_language, - created_at, - updated_at -FROM users -WHERE user_name = $1 AND $1 IS NOT NULL -LIMIT 1 -` - -type GetUserByUserNameRow struct { - ID int64 `json:"id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - UserName string `json:"user_name"` - Email pgtype.Text `json:"email"` - PhoneNumber pgtype.Text `json:"phone_number"` - Role string `json:"role"` - Password []byte `json:"password"` - Age pgtype.Int4 `json:"age"` - EducationLevel pgtype.Text `json:"education_level"` - Country pgtype.Text `json:"country"` - Region pgtype.Text `json:"region"` - NickName pgtype.Text `json:"nick_name"` - Occupation pgtype.Text `json:"occupation"` - LearningGoal pgtype.Text `json:"learning_goal"` - LanguageGoal pgtype.Text `json:"language_goal"` - LanguageChallange pgtype.Text `json:"language_challange"` - FavoutiteTopic pgtype.Text `json:"favoutite_topic"` - EmailVerified bool `json:"email_verified"` - PhoneVerified bool `json:"phone_verified"` - Status string `json:"status"` - ProfileCompleted bool `json:"profile_completed"` - LastLogin pgtype.Timestamptz `json:"last_login"` - ProfilePictureUrl pgtype.Text `json:"profile_picture_url"` - PreferredLanguage pgtype.Text `json:"preferred_language"` - CreatedAt pgtype.Timestamptz `json:"created_at"` - UpdatedAt pgtype.Timestamptz `json:"updated_at"` -} - -func (q *Queries) GetUserByUserName(ctx context.Context, userName string) (GetUserByUserNameRow, error) { - row := q.db.QueryRow(ctx, GetUserByUserName, userName) - var i GetUserByUserNameRow - err := row.Scan( - &i.ID, - &i.FirstName, - &i.LastName, - &i.UserName, - &i.Email, - &i.PhoneNumber, - &i.Role, - &i.Password, - &i.Age, - &i.EducationLevel, - &i.Country, - &i.Region, - &i.NickName, - &i.Occupation, - &i.LearningGoal, - &i.LanguageGoal, - &i.LanguageChallange, - &i.FavoutiteTopic, - &i.EmailVerified, - &i.PhoneVerified, - &i.Status, - &i.ProfileCompleted, - &i.LastLogin, - &i.ProfilePictureUrl, - &i.PreferredLanguage, - &i.CreatedAt, - &i.UpdatedAt, - ) - return i, err -} - const IsProfileCompleted = `-- name: IsProfileCompleted :one SELECT CASE WHEN profile_completed = true THEN true ELSE false END AS is_pending @@ -671,11 +620,11 @@ const IsUserNameUnique = `-- name: IsUserNameUnique :one SELECT CASE WHEN COUNT(*) = 0 THEN true ELSE false END AS is_unique FROM users -WHERE user_name = $1 +WHERE id = $1 ` -func (q *Queries) IsUserNameUnique(ctx context.Context, userName string) (bool, error) { - row := q.db.QueryRow(ctx, IsUserNameUnique, userName) +func (q *Queries) IsUserNameUnique(ctx context.Context, id int64) (bool, error) { + row := q.db.QueryRow(ctx, IsUserNameUnique, id) var is_unique bool err := row.Scan(&is_unique) return is_unique, err @@ -685,12 +634,12 @@ const IsUserPending = `-- name: IsUserPending :one SELECT CASE WHEN status = 'PENDING' THEN true ELSE false END AS is_pending FROM users -WHERE user_name = $1 +WHERE id = $1 LIMIT 1 ` -func (q *Queries) IsUserPending(ctx context.Context, userName string) (bool, error) { - row := q.db.QueryRow(ctx, IsUserPending, userName) +func (q *Queries) IsUserPending(ctx context.Context, id int64) (bool, error) { + row := q.db.QueryRow(ctx, IsUserPending, id) var is_pending bool err := row.Scan(&is_pending) return is_pending, err @@ -701,7 +650,8 @@ SELECT id, first_name, last_name, - user_name, + gender, + birth_day, email, phone_number, role, @@ -715,7 +665,7 @@ SELECT learning_goal, language_goal, language_challange, - favoutite_topic, + favourite_topic, initial_assessment_completed, profile_picture_url, @@ -746,9 +696,10 @@ type SearchUserByNameOrPhoneParams struct { type SearchUserByNameOrPhoneRow struct { ID int64 `json:"id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - UserName string `json:"user_name"` + FirstName pgtype.Text `json:"first_name"` + LastName pgtype.Text `json:"last_name"` + Gender pgtype.Text `json:"gender"` + BirthDay pgtype.Date `json:"birth_day"` Email pgtype.Text `json:"email"` PhoneNumber pgtype.Text `json:"phone_number"` Role string `json:"role"` @@ -761,14 +712,14 @@ type SearchUserByNameOrPhoneRow struct { LearningGoal pgtype.Text `json:"learning_goal"` LanguageGoal pgtype.Text `json:"language_goal"` LanguageChallange pgtype.Text `json:"language_challange"` - FavoutiteTopic pgtype.Text `json:"favoutite_topic"` + FavouriteTopic pgtype.Text `json:"favourite_topic"` InitialAssessmentCompleted bool `json:"initial_assessment_completed"` ProfilePictureUrl pgtype.Text `json:"profile_picture_url"` PreferredLanguage pgtype.Text `json:"preferred_language"` EmailVerified bool `json:"email_verified"` PhoneVerified bool `json:"phone_verified"` Status string `json:"status"` - ProfileCompleted bool `json:"profile_completed"` + ProfileCompleted pgtype.Bool `json:"profile_completed"` CreatedAt pgtype.Timestamptz `json:"created_at"` UpdatedAt pgtype.Timestamptz `json:"updated_at"` } @@ -786,7 +737,8 @@ func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, arg SearchUserByN &i.ID, &i.FirstName, &i.LastName, - &i.UserName, + &i.Gender, + &i.BirthDay, &i.Email, &i.PhoneNumber, &i.Role, @@ -799,7 +751,7 @@ func (q *Queries) SearchUserByNameOrPhone(ctx context.Context, arg SearchUserByN &i.LearningGoal, &i.LanguageGoal, &i.LanguageChallange, - &i.FavoutiteTopic, + &i.FavouriteTopic, &i.InitialAssessmentCompleted, &i.ProfilePictureUrl, &i.PreferredLanguage, @@ -825,16 +777,16 @@ UPDATE users SET password = $1, updated_at = CURRENT_TIMESTAMP -WHERE user_name = $2 +WHERE id = $2 ` type UpdatePasswordParams struct { Password []byte `json:"password"` - UserName string `json:"user_name"` + ID int64 `json:"id"` } func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error { - _, err := q.db.Exec(ctx, UpdatePassword, arg.Password, arg.UserName) + _, err := q.db.Exec(ctx, UpdatePassword, arg.Password, arg.ID) return err } @@ -843,33 +795,37 @@ UPDATE users SET first_name = COALESCE($1, first_name), last_name = COALESCE($2, last_name), - user_name = COALESCE($3, user_name), - knowledge_level = COALESCE($4, knowledge_level), - age = COALESCE($5, age), - education_level = COALESCE($6, education_level), - country = COALESCE($7, country), - region = COALESCE($8, region), - nick_name = COALESCE($9, nick_name), - occupation = COALESCE($10, occupation), - learning_goal = COALESCE($11, learning_goal), - language_goal = COALESCE($12, language_goal), - language_challange = COALESCE($13, language_challange), - favoutite_topic = COALESCE($14, favoutite_topic), - initial_assessment_completed = COALESCE($15, initial_assessment_completed), - email_verified = COALESCE($16, email_verified), - phone_verified = COALESCE($17, phone_verified), - status = COALESCE($18, status), - profile_completed = COALESCE($19, profile_completed), - profile_picture_url = COALESCE($20, profile_picture_url), - preferred_language = COALESCE($21, preferred_language), + + -- email = COALESCE($3, email), + -- phone_number = COALESCE($4, phone_number), + + knowledge_level = COALESCE($3, knowledge_level), + age = COALESCE($4, age), + education_level = COALESCE($5, education_level), + country = COALESCE($6, country), + region = COALESCE($7, region), + nick_name = COALESCE($8, nick_name), + occupation = COALESCE($9, occupation), + learning_goal = COALESCE($10, learning_goal), + language_goal = COALESCE($11, language_goal), + language_challange = COALESCE($12, language_challange), + favourite_topic = COALESCE($13, favourite_topic), + initial_assessment_completed = COALESCE($14, initial_assessment_completed), + -- email_verified = COALESCE($15, email_verified), + -- phone_verified = COALESCE($16, phone_verified), + -- status = COALESCE($19, status), + profile_completed = COALESCE($15, profile_completed), + profile_picture_url = COALESCE($16, profile_picture_url), + preferred_language = COALESCE($17, preferred_language), + gender = COALESCE($18, gender), + birth_day = COALESCE($19, gender), updated_at = CURRENT_TIMESTAMP -WHERE id = $22 +WHERE id = $20 ` type UpdateUserParams struct { - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - UserName string `json:"user_name"` + FirstName pgtype.Text `json:"first_name"` + LastName pgtype.Text `json:"last_name"` KnowledgeLevel pgtype.Text `json:"knowledge_level"` Age pgtype.Int4 `json:"age"` EducationLevel pgtype.Text `json:"education_level"` @@ -880,14 +836,13 @@ type UpdateUserParams struct { LearningGoal pgtype.Text `json:"learning_goal"` LanguageGoal pgtype.Text `json:"language_goal"` LanguageChallange pgtype.Text `json:"language_challange"` - FavoutiteTopic pgtype.Text `json:"favoutite_topic"` + FavouriteTopic pgtype.Text `json:"favourite_topic"` InitialAssessmentCompleted bool `json:"initial_assessment_completed"` - EmailVerified bool `json:"email_verified"` - PhoneVerified bool `json:"phone_verified"` - Status string `json:"status"` - ProfileCompleted bool `json:"profile_completed"` + ProfileCompleted pgtype.Bool `json:"profile_completed"` ProfilePictureUrl pgtype.Text `json:"profile_picture_url"` PreferredLanguage pgtype.Text `json:"preferred_language"` + Gender pgtype.Text `json:"gender"` + BirthDay pgtype.Date `json:"birth_day"` ID int64 `json:"id"` } @@ -895,7 +850,6 @@ func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error { _, err := q.db.Exec(ctx, UpdateUser, arg.FirstName, arg.LastName, - arg.UserName, arg.KnowledgeLevel, arg.Age, arg.EducationLevel, @@ -906,14 +860,13 @@ func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error { arg.LearningGoal, arg.LanguageGoal, arg.LanguageChallange, - arg.FavoutiteTopic, + arg.FavouriteTopic, arg.InitialAssessmentCompleted, - arg.EmailVerified, - arg.PhoneVerified, - arg.Status, arg.ProfileCompleted, arg.ProfilePictureUrl, arg.PreferredLanguage, + arg.Gender, + arg.BirthDay, arg.ID, ) return err diff --git a/internal/domain/otp.go b/internal/domain/otp.go index 543662b..5e1d437 100644 --- a/internal/domain/otp.go +++ b/internal/domain/otp.go @@ -28,7 +28,7 @@ const ( type Otp struct { ID int64 - UserName string + UserID int64 SentTo string Medium OtpMedium For OtpFor diff --git a/internal/domain/user.go b/internal/domain/user.go index dfa153a..22cae6f 100644 --- a/internal/domain/user.go +++ b/internal/domain/user.go @@ -31,10 +31,12 @@ type UpdateKnowledgeLevelReq struct { } type User struct { - ID int64 - FirstName string - LastName string - UserName string + ID int64 + FirstName string + LastName string + Gender string + BirthDay time.Time `json:"birth_day"` + // UserName string Email string PhoneNumber string Password []byte @@ -46,14 +48,14 @@ type User struct { Region string // Profile fields - KnowledgeLevel string - initial_assessment_completed bool - NickName string - Occupation string - LearningGoal string - LanguageGoal string - LanguageChallange string - FavoutiteTopic string + KnowledgeLevel string + InitialAssessmentCompleted bool + NickName string + Occupation string + LearningGoal string + LanguageGoal string + LanguageChallange string + FavouriteTopic string EmailVerified bool PhoneVerified bool @@ -69,10 +71,12 @@ type User struct { } type UserProfileResponse struct { - ID int64 `json:"id"` - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - UserName string `json:"user_name,omitempty"` + ID int64 `json:"id"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Gender string `json:"gender"` + BirthDay time.Time `json:"birth_day"` + // UserName string `json:"user_name,omitempty"` Email string `json:"email,omitempty"` PhoneNumber string `json:"phone_number,omitempty"` Role Role `json:"role"` @@ -89,7 +93,7 @@ type UserProfileResponse struct { LearningGoal string `json:"learning_goal,omitempty"` LanguageGoal string `json:"language_goal,omitempty"` LanguageChallange string `json:"language_challange,omitempty"` - FavoutiteTopic string `json:"favoutite_topic,omitempty"` + FavouriteTopic string `json:"favoutite_topic,omitempty"` EmailVerified bool `json:"email_verified"` PhoneVerified bool `json:"phone_verified"` @@ -107,42 +111,26 @@ type UserProfileResponse struct { type UserFilter struct { Role string - Page ValidInt - PageSize ValidInt - Query ValidString + Page int64 + PageSize int64 + Query string CreatedBefore ValidTime CreatedAfter ValidTime } type RegisterUserReq struct { - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - UserName string `json:"user_name"` - Email string `json:"email"` - PhoneNumber string `json:"phone_number"` - Password string `json:"password"` - Role string `json:"role"` - - OtpMedium OtpMedium `json:"otp_medium"` - - // NickName string `json:"nick_name,omitempty"` - // Occupation string `json:"occupation,omitempty"` - // LearningGoal string `json:"learning_goal,omitempty"` - // LanguageGoal string `json:"language_goal,omitempty"` - // LanguageChallange string `json:"language_challange,omitempty"` - // FavoutiteTopic string `json:"favoutite_topic,omitempty"` - - // Age int `json:"age,omitempty"` - // EducationLevel string `json:"education_level,omitempty"` - // Country string `json:"country,omitempty"` - // Region string `json:"region,omitempty"` - // PreferredLanguage string `json:"preferred_language,omitempty"` + Email string `json:"email"` + PhoneNumber string `json:"phone_number"` + Password string `json:"password"` + Role string `json:"role"` + OtpMedium OtpMedium `json:"otp_medium"` } type CreateUserReq struct { FirstName string LastName string - UserName string + Gender string `json:"gender"` + BirthDay time.Time `json:"birth_day"` Email string PhoneNumber string Password string @@ -161,40 +149,58 @@ type CreateUserReq struct { LearningGoal string LanguageGoal string LanguageChallange string - FavoutiteTopic string + FavouriteTopic string PreferredLanguage string } type ResetPasswordReq struct { - UserName string + UserID int64 Password string OtpCode string } -type UpdateUserReq struct { + +type UpdateUserStatusReq struct { + Status string UserID int64 - - FirstName ValidString - LastName ValidString - UserName ValidString - - Status ValidString - - Age ValidInt - EducationLevel ValidString - Country ValidString - Region ValidString - - // Profile fields - KnowledgeLevel ValidString - NickName ValidString - Occupation ValidString - LearningGoal ValidString - LanguageGoal ValidString - LanguageChallange ValidString - FavoutiteTopic ValidString - - ProfileCompleted ValidBool - ProfilePictureURL ValidString - PreferredLanguage ValidString +} + +type UpdateUserReq struct { + // Identity (enforced from auth context, not request body) + UserID int64 `json:"-"` + + // Basic profile + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + + Gender string `json:"gender"` + BirthDay time.Time `json:"birth_day"` + + // Contact (optional – at least one must exist at DB level) + // Email string `json:"email"` + // PhoneNumber string `json:"phone_number"` + + // Personal details + Age int64 `json:"age"` + EducationLevel string `json:"education_level"` + Country string `json:"country"` + Region string `json:"region"` + + // Learning / profile + KnowledgeLevel string `json:"knowledge_level"` + NickName string `json:"nick_name"` + Occupation string `json:"occupation"` + LearningGoal string `json:"learning_goal"` + LanguageGoal string `json:"language_goal"` + LanguageChallange string `json:"language_challange"` + FavouriteTopic string `json:"favourite_topic"` + + InitialAssessmentCompleted bool `json:"initial_assessment_completed"` + // EmailVerified bool `json:"email_verified"` + // PhoneVerified bool `json:"phone_verified"` + ProfileCompleted bool `json:"profile_completed"` + + // Media & preferences + ProfilePictureURL string `json:"profile_picture_url"` + PreferredLanguage string `json:"preferred_language"` } diff --git a/internal/ports/user.go b/internal/ports/user.go index 3575d64..a0cd54f 100644 --- a/internal/ports/user.go +++ b/internal/ports/user.go @@ -9,7 +9,7 @@ import ( type UserStore interface { IsProfileCompleted(ctx context.Context, userId int64) (bool, error) - UpdateUserStatus(ctx context.Context, user domain.UpdateUserReq) error + UpdateUserStatus(ctx context.Context, req domain.UpdateUserStatusReq) error // GetCorrectOptionForQuestion( // ctx context.Context, // questionID int64, @@ -19,12 +19,12 @@ type UserStore interface { // userID int64, // ) (*dbgen.AssessmentAttempt, error) UpdateUserKnowledgeLevel(ctx context.Context, userID int64, knowledgeLevel string) error - IsUserNameUnique(ctx context.Context, userName string) (bool, error) - IsUserPending(ctx context.Context, UserName string) (bool, error) - GetUserByUserName( - ctx context.Context, - userName string, - ) (domain.User, error) + // IsUserNameUnique(ctx context.Context, userName string) (bool, error) + IsUserPending(ctx context.Context, userID int64) (bool, error) + // GetUserByUserName( + // ctx context.Context, + // userName string, + // ) (domain.User, error) CreateUser( ctx context.Context, user domain.User, @@ -51,7 +51,7 @@ type UserStore interface { search string, role *string, ) ([]domain.User, error) - UpdateUser(ctx context.Context, user domain.User) error + UpdateUser(ctx context.Context, req domain.UpdateUserReq) error DeleteUser(ctx context.Context, userID int64) error CheckPhoneEmailExist(ctx context.Context, phone, email string) (phoneExists, emailExists bool, err error) GetUserByEmailPhone( @@ -59,7 +59,7 @@ type UserStore interface { email string, phone string, ) (domain.User, error) - UpdatePassword(ctx context.Context, password, userName string) error + UpdatePassword(ctx context.Context, password string, userID int64) error } type SmsGateway interface { SendSMSOTP(ctx context.Context, phoneNumber, otp string) error @@ -68,8 +68,8 @@ type EmailGateway interface { SendEmailOTP(ctx context.Context, email string, otp string) error } type OtpStore interface { - UpdateOtp(ctx context.Context, otp, userName string) error + UpdateOtp(ctx context.Context, otp string, userID int64) error MarkOtpAsUsed(ctx context.Context, otp domain.Otp) error CreateOtp(ctx context.Context, otp domain.Otp) error - GetOtp(ctx context.Context, userName string) (domain.Otp, error) + GetOtp(ctx context.Context, userID int64) (domain.Otp, error) } diff --git a/internal/repository/auth.go b/internal/repository/auth.go index 0458310..093d815 100644 --- a/internal/repository/auth.go +++ b/internal/repository/auth.go @@ -117,10 +117,10 @@ func (s *Store) GetUserByEmailOrPhone( } return domain.User{ - ID: u.ID, - FirstName: u.FirstName, - LastName: u.LastName, - UserName: u.UserName, + ID: u.ID, + FirstName: u.FirstName.String, + LastName: u.LastName.String, + // UserName: u.UserName, Email: u.Email.String, PhoneNumber: u.PhoneNumber.String, Password: u.Password, @@ -136,7 +136,7 @@ func (s *Store) GetUserByEmailOrPhone( Status: domain.UserStatus(u.Status), LastLogin: lastLogin, - ProfileCompleted: u.ProfileCompleted, + ProfileCompleted: u.ProfileCompleted.Bool, ProfilePictureURL: u.ProfilePictureUrl.String, PreferredLanguage: u.PreferredLanguage.String, diff --git a/internal/repository/otp.go b/internal/repository/otp.go index 862027c..ebba610 100644 --- a/internal/repository/otp.go +++ b/internal/repository/otp.go @@ -16,10 +16,10 @@ import ( // Interface for creating new otp store func NewOTPStore(s *Store) ports.OtpStore { return s } -func (s *Store) UpdateOtp(ctx context.Context, otp, userName string) error { +func (s *Store) UpdateOtp(ctx context.Context, otp string, userId int64) error { return s.queries.UpdateExpiredOtp(ctx, dbgen.UpdateExpiredOtpParams{ - UserName: userName, - Otp: otp, + UserID: userId, + Otp: otp, ExpiresAt: pgtype.Timestamptz{ Time: time.Now().Add(5 * time.Minute), Valid: true, @@ -29,11 +29,11 @@ func (s *Store) UpdateOtp(ctx context.Context, otp, userName string) error { func (s *Store) CreateOtp(ctx context.Context, otp domain.Otp) error { return s.queries.CreateOtp(ctx, dbgen.CreateOtpParams{ - UserName: otp.UserName, - SentTo: otp.SentTo, - Medium: string(otp.Medium), - OtpFor: string(otp.For), - Otp: otp.Otp, + UserID: otp.UserID, + SentTo: otp.SentTo, + Medium: string(otp.Medium), + OtpFor: string(otp.For), + Otp: otp.Otp, ExpiresAt: pgtype.Timestamptz{ Time: otp.ExpiresAt, Valid: true, @@ -41,10 +41,10 @@ func (s *Store) CreateOtp(ctx context.Context, otp domain.Otp) error { }) } -func (s *Store) GetOtp(ctx context.Context, userName string) (domain.Otp, error) { - row, err := s.queries.GetOtp(ctx, userName) +func (s *Store) GetOtp(ctx context.Context, userID int64) (domain.Otp, error) { + row, err := s.queries.GetOtp(ctx, userID) if err != nil { - fmt.Printf("OTP REPO error: %v userName: %v\n", err, userName) + fmt.Printf("OTP REPO error: %v userName: %v\n", err, userID) if err == sql.ErrNoRows { return domain.Otp{}, domain.ErrOtpNotFound } @@ -52,7 +52,7 @@ func (s *Store) GetOtp(ctx context.Context, userName string) (domain.Otp, error) } return domain.Otp{ ID: row.ID, - UserName: row.UserName, + UserID: row.UserID, SentTo: row.SentTo, Medium: domain.OtpMedium(row.Medium), For: domain.OtpFor(row.OtpFor), diff --git a/internal/repository/user.go b/internal/repository/user.go index 67348ae..2dab2f7 100644 --- a/internal/repository/user.go +++ b/internal/repository/user.go @@ -34,8 +34,8 @@ func (s *Store) UpdateUserKnowledgeLevel(ctx context.Context, userID int64, know }) } -func (s *Store) IsUserPending(ctx context.Context, UserName string) (bool, error) { - isPending, err := s.queries.IsUserPending(ctx, UserName) +func (s *Store) IsUserPending(ctx context.Context, userID int64) (bool, error) { + isPending, err := s.queries.IsUserPending(ctx, userID) if err != nil { if errors.Is(err, sql.ErrNoRows) { return false, authentication.ErrUserNotFound @@ -45,18 +45,18 @@ func (s *Store) IsUserPending(ctx context.Context, UserName string) (bool, error return isPending, nil } -func (s *Store) IsUserNameUnique(ctx context.Context, userName string) (bool, error) { - isUnique, err := s.queries.IsUserNameUnique(ctx, userName) +func (s *Store) IsUserNameUnique(ctx context.Context, userID int64) (bool, error) { + isUnique, err := s.queries.IsUserNameUnique(ctx, userID) if err != nil { return false, err } return isUnique, nil } -func (s *Store) UpdateUserStatus(ctx context.Context, user domain.UpdateUserReq) error { +func (s *Store) UpdateUserStatus(ctx context.Context, req domain.UpdateUserStatusReq) error { return s.queries.UpdateUserStatus(ctx, dbgen.UpdateUserStatusParams{ - Status: user.Status.Value, - ID: user.UserID, + Status: req.Status, + ID: req.UserID, }) } @@ -66,9 +66,17 @@ func (s *Store) CreateUserWithoutOtp( ) (domain.User, error) { userRes, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{ - FirstName: user.FirstName, - LastName: user.LastName, - UserName: user.UserName, + FirstName: pgtype.Text{String: user.FirstName}, + LastName: pgtype.Text{String: user.LastName}, + Gender: pgtype.Text{ + String: user.Gender, + Valid: user.Gender != "", + }, + BirthDay: pgtype.Date{ + Time: user.BirthDay, + Valid: true, + }, + // UserName: user.UserName, Email: pgtype.Text{String: user.Email, Valid: user.Email != ""}, PhoneNumber: pgtype.Text{String: user.PhoneNumber, Valid: user.PhoneNumber != ""}, @@ -101,9 +109,9 @@ func (s *Store) CreateUserWithoutOtp( String: user.LanguageChallange, Valid: user.LanguageChallange != "", }, - FavoutiteTopic: pgtype.Text{ - String: user.FavoutiteTopic, - Valid: user.FavoutiteTopic != "", + FavouriteTopic: pgtype.Text{ + String: user.FavouriteTopic, + Valid: user.FavouriteTopic != "", }, EmailVerified: user.EmailVerified, @@ -113,8 +121,11 @@ func (s *Store) CreateUserWithoutOtp( String: user.ProfilePictureURL, Valid: user.ProfilePictureURL != "", }, - Status: string(user.Status), - ProfileCompleted: user.ProfileCompleted, + Status: string(user.Status), + ProfileCompleted: pgtype.Bool{ + Bool: user.ProfileCompleted, + Valid: true, + }, PreferredLanguage: pgtype.Text{ String: user.PreferredLanguage, Valid: user.PreferredLanguage != "", @@ -149,9 +160,17 @@ func (s *Store) CreateUser( } userRes, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{ - FirstName: user.FirstName, - LastName: user.LastName, - UserName: user.UserName, + FirstName: pgtype.Text{String: user.FirstName}, + LastName: pgtype.Text{String: user.LastName}, + Gender: pgtype.Text{ + String: user.Gender, + Valid: user.Gender != "", + }, + BirthDay: pgtype.Date{ + Time: user.BirthDay, + Valid: true, + }, + // UserName: user.UserName, Email: pgtype.Text{String: user.Email, Valid: user.Email != ""}, PhoneNumber: pgtype.Text{String: user.PhoneNumber, Valid: user.PhoneNumber != ""}, @@ -169,7 +188,7 @@ func (s *Store) CreateUser( LearningGoal: pgtype.Text{String: user.LearningGoal, Valid: user.LearningGoal != ""}, LanguageGoal: pgtype.Text{String: user.LanguageGoal, Valid: user.LanguageGoal != ""}, LanguageChallange: pgtype.Text{String: user.LanguageChallange, Valid: user.LanguageChallange != ""}, - FavoutiteTopic: pgtype.Text{String: user.FavoutiteTopic, Valid: user.FavoutiteTopic != ""}, + FavouriteTopic: pgtype.Text{String: user.FavouriteTopic, Valid: user.FavouriteTopic != ""}, EmailVerified: user.EmailVerified, PhoneVerified: user.PhoneVerified, @@ -178,8 +197,11 @@ func (s *Store) CreateUser( String: user.ProfilePictureURL, Valid: user.ProfilePictureURL != "", }, - Status: string(user.Status), - ProfileCompleted: user.ProfileCompleted, + Status: string(user.Status), + ProfileCompleted: pgtype.Bool{ + Bool: user.ProfileCompleted, + Valid: true, + }, PreferredLanguage: pgtype.Text{ String: user.PreferredLanguage, Valid: user.PreferredLanguage != "", @@ -222,10 +244,12 @@ func (s *Store) GetUserByID( } return domain.User{ - ID: u.ID, - FirstName: u.FirstName, - LastName: u.LastName, - UserName: u.UserName, + ID: u.ID, + FirstName: u.FirstName.String, + LastName: u.LastName.String, + Gender: u.Gender.String, + BirthDay: u.BirthDay.Time, + // UserName: u.UserName, Email: u.Email.String, PhoneNumber: u.PhoneNumber.String, Role: domain.Role(u.Role), @@ -240,14 +264,14 @@ func (s *Store) GetUserByID( LearningGoal: u.LearningGoal.String, LanguageGoal: u.LanguageGoal.String, LanguageChallange: u.LanguageChallange.String, - FavoutiteTopic: u.FavoutiteTopic.String, + FavouriteTopic: u.FavouriteTopic.String, EmailVerified: u.EmailVerified, PhoneVerified: u.PhoneVerified, Status: domain.UserStatus(u.Status), LastLogin: lastLogin, - ProfileCompleted: u.ProfileCompleted, + ProfileCompleted: u.ProfileCompleted.Bool, ProfilePictureURL: u.ProfilePictureUrl.String, PreferredLanguage: u.PreferredLanguage.String, @@ -322,10 +346,12 @@ func (s *Store) GetAllUsers( } users = append(users, domain.User{ - ID: u.ID, - FirstName: u.FirstName, - LastName: u.LastName, - UserName: u.UserName, + ID: u.ID, + FirstName: u.FirstName.String, + LastName: u.LastName.String, + Gender: u.Gender.String, + BirthDay: u.BirthDay.Time, + // UserName: u.UserName, Email: u.Email.String, PhoneNumber: u.PhoneNumber.String, Role: domain.Role(u.Role), @@ -340,14 +366,14 @@ func (s *Store) GetAllUsers( LearningGoal: u.LearningGoal.String, LanguageGoal: u.LanguageGoal.String, LanguageChallange: u.LanguageChallange.String, - FavoutiteTopic: u.FavoutiteTopic.String, + FavouriteTopic: u.FavouriteTopic.String, EmailVerified: u.EmailVerified, PhoneVerified: u.PhoneVerified, Status: domain.UserStatus(u.Status), ProfilePictureURL: u.ProfilePictureUrl.String, - ProfileCompleted: u.ProfileCompleted, + ProfileCompleted: u.ProfileCompleted.Bool, PreferredLanguage: u.PreferredLanguage.String, CreatedAt: u.CreatedAt.Time, @@ -407,10 +433,12 @@ func (s *Store) SearchUserByNameOrPhone( } users = append(users, domain.User{ - ID: u.ID, - FirstName: u.FirstName, - LastName: u.LastName, - UserName: u.UserName, + ID: u.ID, + FirstName: u.FirstName.String, + LastName: u.LastName.String, + Gender: u.Gender.String, + BirthDay: u.BirthDay.Time, + // UserName: u.UserName, Email: u.Email.String, PhoneNumber: u.PhoneNumber.String, Role: domain.Role(u.Role), @@ -425,13 +453,13 @@ func (s *Store) SearchUserByNameOrPhone( LearningGoal: u.LearningGoal.String, LanguageGoal: u.LanguageGoal.String, LanguageChallange: u.LanguageChallange.String, - FavoutiteTopic: u.FavoutiteTopic.String, + FavouriteTopic: u.FavouriteTopic.String, EmailVerified: u.EmailVerified, PhoneVerified: u.PhoneVerified, Status: domain.UserStatus(u.Status), - ProfileCompleted: u.ProfileCompleted, + ProfileCompleted: u.ProfileCompleted.Bool, ProfilePictureURL: u.ProfilePictureUrl.String, PreferredLanguage: u.PreferredLanguage.String, @@ -446,69 +474,44 @@ func (s *Store) SearchUserByNameOrPhone( // UpdateUser updates basic user info func (s *Store) UpdateUser( ctx context.Context, - user domain.User, + req domain.UpdateUserReq, ) error { return s.queries.UpdateUser(ctx, dbgen.UpdateUserParams{ - FirstName: user.FirstName, - LastName: user.LastName, - UserName: user.UserName, - - Age: pgtype.Int4{ - Int32: int32(user.Age), - Valid: user.Age > 0, + FirstName: pgtype.Text{String: req.FirstName, Valid: req.FirstName != ""}, + LastName: pgtype.Text{String: req.LastName, Valid: req.LastName != ""}, + Gender: pgtype.Text{ + String: req.Gender, + Valid: req.Gender != "", }, - EducationLevel: pgtype.Text{ - String: user.EducationLevel, - Valid: user.EducationLevel != "", - }, - Country: pgtype.Text{ - String: user.Country, - Valid: user.Country != "", - }, - Region: pgtype.Text{ - String: user.Region, - Valid: user.Region != "", + BirthDay: pgtype.Date{ + Time: req.BirthDay, + Valid: true, }, - NickName: pgtype.Text{ - String: user.NickName, - Valid: user.NickName != "", - }, - Occupation: pgtype.Text{ - String: user.Occupation, - Valid: user.Occupation != "", - }, - LearningGoal: pgtype.Text{ - String: user.LearningGoal, - Valid: user.LearningGoal != "", - }, - LanguageGoal: pgtype.Text{ - String: user.LanguageGoal, - Valid: user.LanguageGoal != "", - }, - LanguageChallange: pgtype.Text{ - String: user.LanguageChallange, - Valid: user.LanguageChallange != "", - }, - FavoutiteTopic: pgtype.Text{ - String: user.FavoutiteTopic, - Valid: user.FavoutiteTopic != "", + Age: pgtype.Int4{Int32: int32(req.Age), Valid: req.Age > 0}, + EducationLevel: pgtype.Text{String: req.EducationLevel, Valid: req.EducationLevel != ""}, + Country: pgtype.Text{String: req.Country, Valid: req.Country != ""}, + Region: pgtype.Text{String: req.Region, Valid: req.Region != ""}, + + NickName: pgtype.Text{String: req.NickName, Valid: req.NickName != ""}, + Occupation: pgtype.Text{String: req.Occupation, Valid: req.Occupation != ""}, + LearningGoal: pgtype.Text{String: req.LearningGoal, Valid: req.LearningGoal != ""}, + LanguageGoal: pgtype.Text{String: req.LanguageGoal, Valid: req.LanguageGoal != ""}, + LanguageChallange: pgtype.Text{String: req.LanguageChallange, Valid: req.LanguageChallange != ""}, + FavouriteTopic: pgtype.Text{String: req.FavouriteTopic, Valid: req.FavouriteTopic != ""}, + + ProfileCompleted: pgtype.Bool{ + Bool: req.ProfileCompleted, + Valid: true, }, - Status: string(user.Status), - ProfileCompleted: user.ProfileCompleted, - ProfilePictureUrl: pgtype.Text{ - String: user.ProfilePictureURL, - Valid: user.ProfilePictureURL != "", - }, - PreferredLanguage: pgtype.Text{ - String: user.PreferredLanguage, - Valid: user.PreferredLanguage != "", - }, + ProfilePictureUrl: pgtype.Text{String: req.ProfilePictureURL, Valid: req.ProfilePictureURL != ""}, + PreferredLanguage: pgtype.Text{String: req.PreferredLanguage, Valid: req.PreferredLanguage != ""}, - ID: user.ID, + ID: req.UserID, }) + } // DeleteUser removes a user @@ -529,64 +532,64 @@ func (s *Store) CheckPhoneEmailExist(ctx context.Context, phone, email string) ( return res.PhoneExists, res.EmailExists, nil } -func (s *Store) GetUserByUserName( - ctx context.Context, - userName string, -) (domain.User, error) { +// func (s *Store) GetUserByUserName( +// ctx context.Context, +// userName string, +// ) (domain.User, error) { - u, err := s.queries.GetUserByUserName(ctx, userName) - if err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return domain.User{}, authentication.ErrUserNotFound - } - return domain.User{}, err - } +// u, err := s.queries.GetUserByUserName(ctx, userName) +// if err != nil { +// if errors.Is(err, pgx.ErrNoRows) { +// return domain.User{}, authentication.ErrUserNotFound +// } +// return domain.User{}, err +// } - var lastLogin *time.Time - if u.LastLogin.Valid { - lastLogin = &u.LastLogin.Time - } +// var lastLogin *time.Time +// if u.LastLogin.Valid { +// lastLogin = &u.LastLogin.Time +// } - var updatedAt *time.Time - if u.UpdatedAt.Valid { - updatedAt = &u.UpdatedAt.Time - } +// var updatedAt *time.Time +// if u.UpdatedAt.Valid { +// updatedAt = &u.UpdatedAt.Time +// } - return domain.User{ - ID: u.ID, - FirstName: u.FirstName, - LastName: u.LastName, - UserName: u.UserName, - Email: u.Email.String, - PhoneNumber: u.PhoneNumber.String, - Password: u.Password, - Role: domain.Role(u.Role), +// return domain.User{ +// ID: u.ID, +// FirstName: u.FirstName, +// LastName: u.LastName, +// UserName: u.UserName, +// Email: u.Email.String, +// PhoneNumber: u.PhoneNumber.String, +// Password: u.Password, +// Role: domain.Role(u.Role), - Age: int(u.Age.Int32), - EducationLevel: u.EducationLevel.String, - Country: u.Country.String, - Region: u.Region.String, +// Age: int(u.Age.Int32), +// EducationLevel: u.EducationLevel.String, +// Country: u.Country.String, +// Region: u.Region.String, - NickName: u.NickName.String, - Occupation: u.Occupation.String, - LearningGoal: u.LearningGoal.String, - LanguageGoal: u.LanguageGoal.String, - LanguageChallange: u.LanguageChallange.String, - FavoutiteTopic: u.FavoutiteTopic.String, +// NickName: u.NickName.String, +// Occupation: u.Occupation.String, +// LearningGoal: u.LearningGoal.String, +// LanguageGoal: u.LanguageGoal.String, +// LanguageChallange: u.LanguageChallange.String, +// FavouriteTopic: u.FavouriteTopic.String, - EmailVerified: u.EmailVerified, - PhoneVerified: u.PhoneVerified, - Status: domain.UserStatus(u.Status), +// EmailVerified: u.EmailVerified, +// PhoneVerified: u.PhoneVerified, +// Status: domain.UserStatus(u.Status), - LastLogin: lastLogin, - ProfileCompleted: u.ProfileCompleted, - ProfilePictureURL: u.ProfilePictureUrl.String, - PreferredLanguage: u.PreferredLanguage.String, +// LastLogin: lastLogin, +// ProfileCompleted: u.ProfileCompleted, +// ProfilePictureURL: u.ProfilePictureUrl.String, +// PreferredLanguage: u.PreferredLanguage.String, - CreatedAt: u.CreatedAt.Time, - UpdatedAt: updatedAt, - }, nil -} +// CreatedAt: u.CreatedAt.Time, +// UpdatedAt: updatedAt, +// }, nil +// } // GetUserByEmail retrieves a user by email and organization func (s *Store) GetUserByEmailPhone( @@ -623,10 +626,12 @@ func (s *Store) GetUserByEmailPhone( } return domain.User{ - ID: u.ID, - FirstName: u.FirstName, - LastName: u.LastName, - UserName: u.UserName, + ID: u.ID, + FirstName: u.FirstName.String, + LastName: u.LastName.String, + Gender: u.Gender.String, + BirthDay: u.BirthDay.Time, + // UserName: u.UserName, Email: u.Email.String, PhoneNumber: u.PhoneNumber.String, Password: u.Password, @@ -642,7 +647,7 @@ func (s *Store) GetUserByEmailPhone( LearningGoal: u.LearningGoal.String, LanguageGoal: u.LanguageGoal.String, LanguageChallange: u.LanguageChallange.String, - FavoutiteTopic: u.FavoutiteTopic.String, + FavouriteTopic: u.FavouriteTopic.String, EmailVerified: u.EmailVerified, PhoneVerified: u.PhoneVerified, @@ -650,7 +655,7 @@ func (s *Store) GetUserByEmailPhone( ProfilePictureURL: u.ProfilePictureUrl.String, LastLogin: lastLogin, - ProfileCompleted: u.ProfileCompleted, + ProfileCompleted: u.ProfileCompleted.Bool, PreferredLanguage: u.PreferredLanguage.String, CreatedAt: u.CreatedAt.Time, @@ -659,10 +664,10 @@ func (s *Store) GetUserByEmailPhone( } // UpdatePassword updates a user's password -func (s *Store) UpdatePassword(ctx context.Context, password, userName string) error { +func (s *Store) UpdatePassword(ctx context.Context, password string, userID int64) error { return s.queries.UpdatePassword(ctx, dbgen.UpdatePasswordParams{ Password: []byte(password), - UserName: userName, + ID: userID, }) } @@ -674,10 +679,12 @@ func mapCreateUserResult( ) domain.User { return domain.User{ - ID: userRes.ID, - FirstName: userRes.FirstName, - LastName: userRes.LastName, - UserName: userRes.UserName, + ID: userRes.ID, + FirstName: userRes.FirstName.String, + LastName: userRes.LastName.String, + Gender: userRes.Gender.String, + BirthDay: userRes.BirthDay.Time, + // UserName: userRes.UserName, Email: userRes.Email.String, PhoneNumber: userRes.PhoneNumber.String, Role: domain.Role(userRes.Role), @@ -693,13 +700,13 @@ func mapCreateUserResult( LearningGoal: userRes.LearningGoal.String, LanguageGoal: userRes.LanguageGoal.String, LanguageChallange: userRes.LanguageChallange.String, - FavoutiteTopic: userRes.FavoutiteTopic.String, + FavouriteTopic: userRes.FavouriteTopic.String, EmailVerified: userRes.EmailVerified, PhoneVerified: userRes.PhoneVerified, Status: domain.UserStatus(userRes.Status), - ProfileCompleted: userRes.ProfileCompleted, + ProfileCompleted: userRes.ProfileCompleted.Bool, PreferredLanguage: userRes.PreferredLanguage.String, CreatedAt: userRes.CreatedAt.Time, diff --git a/internal/services/user/common.go b/internal/services/user/common.go index c67af5d..6cfb130 100644 --- a/internal/services/user/common.go +++ b/internal/services/user/common.go @@ -17,7 +17,7 @@ func (s *Service) VerifyOtp(ctx context.Context, email, phone, otpCode string) e return err } // 1. Retrieve the OTP from the store - storedOtp, err := s.otpStore.GetOtp(ctx, user.UserName) + storedOtp, err := s.otpStore.GetOtp(ctx, user.ID) if err != nil { return err // could be ErrOtpNotFound or other DB errors } @@ -50,12 +50,9 @@ func (s *Service) VerifyOtp(ctx context.Context, email, phone, otpCode string) e // return err // } - newUser := domain.UpdateUserReq{ + newUser := domain.UpdateUserStatusReq{ UserID: user.ID, - Status: domain.ValidString{ - Value: string(domain.UserStatusActive), - Valid: true, - }, + Status: string(domain.UserStatusActive), } s.userStore.UpdateUserStatus(ctx, newUser) @@ -80,7 +77,7 @@ func (s *Service) ResendOtp( otpCode, ) - otp, err := s.otpStore.GetOtp(ctx, user.UserName) + otp, err := s.otpStore.GetOtp(ctx, user.ID) if err != nil { return err } @@ -106,14 +103,14 @@ func (s *Service) ResendOtp( return fmt.Errorf("invalid otp medium: %s", otp.Medium) } - if err := s.otpStore.UpdateOtp(ctx, otpCode, user.UserName); err != nil { + if err := s.otpStore.UpdateOtp(ctx, otpCode, user.ID); err != nil { return err } return nil } -func (s *Service) SendOtp(ctx context.Context, userName string, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium, provider domain.SMSProvider) error { +func (s *Service) SendOtp(ctx context.Context, userID int64, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium, provider domain.SMSProvider) error { otpCode := helpers.GenerateOTP() message := fmt.Sprintf("Welcome to Yimaru Online Learning Platform, your OTP is %s please don't share with anyone.", otpCode) @@ -140,7 +137,7 @@ func (s *Service) SendOtp(ctx context.Context, userName string, sentTo string, o } otp := domain.Otp{ - UserName: userName, + UserID: userID, SentTo: sentTo, Medium: medium, For: otpFor, diff --git a/internal/services/user/direct.go b/internal/services/user/direct.go index 20d7774..13af1be 100644 --- a/internal/services/user/direct.go +++ b/internal/services/user/direct.go @@ -25,7 +25,7 @@ func (s *Service) CreateUser( return s.userStore.CreateUserWithoutOtp(ctx, domain.User{ FirstName: req.FirstName, LastName: req.LastName, - UserName: req.UserName, + // UserName: req.UserName, Email: req.Email, PhoneNumber: req.PhoneNumber, Password: hashedPassword, @@ -48,7 +48,7 @@ func (s *Service) DeleteUser(ctx context.Context, id int64) error { func (s *Service) GetAllUsers(ctx context.Context, filter domain.UserFilter) ([]domain.User, int64, error) { // Get all Users - return s.userStore.GetAllUsers(ctx, &filter.Role, &filter.Query.Value, &filter.CreatedBefore.Value, &filter.CreatedAfter.Value, int32(filter.PageSize.Value), int32(filter.Page.Value)) + return s.userStore.GetAllUsers(ctx, &filter.Role, &filter.Query, &filter.CreatedBefore.Value, &filter.CreatedAfter.Value, int32(filter.PageSize), int32(filter.Page)) } func (s *Service) GetUserById(ctx context.Context, id int64) (domain.User, error) { diff --git a/internal/services/user/register.go b/internal/services/user/register.go index 6d24a70..a1fbbe2 100644 --- a/internal/services/user/register.go +++ b/internal/services/user/register.go @@ -26,7 +26,7 @@ func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, } // send otp based on the medium - return s.SendOtp(ctx, "", sentTo, domain.OtpRegister, medium, provider) + return s.SendOtp(ctx, 0, sentTo, domain.OtpRegister, medium, provider) } func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) { @@ -54,21 +54,21 @@ func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterU // Prepare the user userR := domain.User{ - FirstName: registerReq.FirstName, - LastName: registerReq.LastName, - UserName: registerReq.UserName, - Email: registerReq.Email, - PhoneNumber: registerReq.PhoneNumber, - Password: hashedPassword, - Role: domain.RoleStudent, - EmailVerified: false, - PhoneVerified: false, + // FirstName: registerReq.FirstName, + // LastName: registerReq.LastName, + // UserName: registerReq.UserName, + Email: registerReq.Email, + PhoneNumber: registerReq.PhoneNumber, + Password: hashedPassword, + Role: domain.RoleStudent, + EmailVerified: false, + PhoneVerified: false, // EducationLevel: registerReq.EducationLevel, // Age: registerReq.Age, // Country: registerReq.Country, // Region: registerReq.Region, - Status: domain.UserStatusPending, - ProfileCompleted: false, + Status: domain.UserStatusPending, + ProfileCompleted: false, // PreferredLanguage: registerReq.PreferredLanguage, // Optional fields @@ -92,9 +92,6 @@ func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterU } // Send OTP to the user (email/SMS) - if err := s.SendOtp(ctx, registerReq.UserName, sentTo, domain.OtpRegister, registerReq.OtpMedium, domain.TwilioSms); err != nil { - return domain.User{}, err - } // Create the user (no OTP validation yet) user, err := s.userStore.CreateUserWithoutOtp(ctx, userR) @@ -102,5 +99,9 @@ func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterU return domain.User{}, err } + if err := s.SendOtp(ctx, user.ID, sentTo, domain.OtpRegister, registerReq.OtpMedium, domain.TwilioSms); err != nil { + return domain.User{}, err + } + return user, nil } diff --git a/internal/services/user/reset.go b/internal/services/user/reset.go index 3fe50b4..a3ccfb4 100644 --- a/internal/services/user/reset.go +++ b/internal/services/user/reset.go @@ -7,7 +7,7 @@ import ( "time" ) -func (s *Service) SendResetCode(ctx context.Context, userName string, medium domain.OtpMedium, sentTo string, provider domain.SMSProvider) error { +func (s *Service) SendResetCode(ctx context.Context, userID int64, medium domain.OtpMedium, sentTo string, provider domain.SMSProvider) error { var err error // check if user exists @@ -22,13 +22,13 @@ func (s *Service) SendResetCode(ctx context.Context, userName string, medium dom return err } - return s.SendOtp(ctx, userName, sentTo, domain.OtpReset, medium, provider) + return s.SendOtp(ctx, userID, sentTo, domain.OtpReset, medium, provider) } func (s *Service) ResetPassword(ctx context.Context, resetReq domain.ResetPasswordReq) error { - otp, err := s.otpStore.GetOtp(ctx, resetReq.UserName) + otp, err := s.otpStore.GetOtp(ctx, resetReq.UserID) if err != nil { return err } @@ -48,7 +48,7 @@ func (s *Service) ResetPassword(ctx context.Context, resetReq domain.ResetPasswo return domain.ErrInvalidOtp } - err = s.userStore.UpdatePassword(ctx, resetReq.Password, resetReq.UserName) + err = s.userStore.UpdatePassword(ctx, resetReq.Password, resetReq.UserID) if err != nil { return err } diff --git a/internal/services/user/user.go b/internal/services/user/user.go index 40a8cbb..fe4827c 100644 --- a/internal/services/user/user.go +++ b/internal/services/user/user.go @@ -14,24 +14,24 @@ func (s *Service) GetUserByEmailPhone( return s.userStore.GetUserByEmailPhone(ctx, email, phone) } -func (s *Service) IsUserPending(ctx context.Context, userName string) (bool, error) { - return s.userStore.IsUserPending(ctx, userName) +func (s *Service) IsUserPending(ctx context.Context, userID int64) (bool, error) { + return s.userStore.IsUserPending(ctx, userID) } func (s *Service) IsProfileCompleted(ctx context.Context, userId int64) (bool, error) { return s.userStore.IsProfileCompleted(ctx, userId) } -func (s *Service) IsUserNameUnique(ctx context.Context, userName string) (bool, error) { - return s.userStore.IsUserNameUnique(ctx, userName) -} +// func (s *Service) IsUserNameUnique(ctx context.Context, userID int64) (bool, error) { +// return s.userStore.IsUserNameUnique(ctx, userID) +// } -func (s *Service) GetUserByUserName( - ctx context.Context, - userName string, -) (domain.User, error) { - return s.userStore.GetUserByUserName(ctx, userName) -} +// func (s *Service) GetUserByUserName( +// ctx context.Context, +// userName string, +// ) (domain.User, error) { +// return s.userStore.GetUserByUserName(ctx, userName) +// } func (s *Service) SearchUserByNameOrPhone(ctx context.Context, searchString string, role *int64) ([]domain.User, error) { // Search user @@ -45,29 +45,8 @@ func (s *Service) SearchUserByNameOrPhone(ctx context.Context, searchString stri } func (s *Service) UpdateUser(ctx context.Context, req domain.UpdateUserReq) error { - newUser := domain.User{ - ID: req.UserID, - FirstName: req.FirstName.Value, - LastName: req.LastName.Value, - KnowledgeLevel: req.KnowledgeLevel.Value, - UserName: req.UserName.Value, - Age: req.Age.Value, - EducationLevel: req.EducationLevel.Value, - Country: req.Country.Value, - Region: req.Region.Value, - Status: domain.UserStatus(req.Status.Value), - NickName: req.NickName.Value, - Occupation: req.Occupation.Value, - LearningGoal: req.LearningGoal.Value, - LanguageGoal: req.LanguageGoal.Value, - LanguageChallange: req.LanguageChallange.Value, - FavoutiteTopic: req.FavoutiteTopic.Value, - PreferredLanguage: req.PreferredLanguage.Value, - ProfilePictureURL: req.ProfilePictureURL.Value, - } - // Update user in the store - return s.userStore.UpdateUser(ctx, newUser) + return s.userStore.UpdateUser(ctx, req) } // func (s *Service) UpdateUserSuspend(ctx context.Context, id int64, status bool) error { @@ -87,29 +66,29 @@ func (s *Service) GetUserByID(ctx context.Context, id int64) (domain.User, error // var query *string // if filter.Query.Valid { -// q := filter.Query.Value +// q := filter.Query // query = &q // } // var createdBefore *time.Time // if filter.CreatedBefore.Valid { -// b := filter.CreatedBefore.Value +// b := filter.CreatedBefore // createdBefore = &b // } // var createdAfter *time.Time // if filter.CreatedAfter.Valid { -// a := filter.CreatedAfter.Value +// a := filter.CreatedAfter // createdAfter = &a // } // var limit int32 = 10 // var offset int32 = 0 // if filter.PageSize.Valid { -// limit = int32(filter.PageSize.Value) +// limit = int32(filter.PageSize) // } // if filter.Page.Valid && filter.PageSize.Valid { -// offset = int32(filter.Page.Value * filter.PageSize.Value) +// offset = int32(filter.Page * filter.PageSize) // } // return s.userStore.GetAllUsers(ctx, role, query, createdBefore, createdAfter, limit, offset) diff --git a/internal/web_server/handlers/admin.go b/internal/web_server/handlers/admin.go index 6130afb..9133974 100644 --- a/internal/web_server/handlers/admin.go +++ b/internal/web_server/handlers/admin.go @@ -13,11 +13,11 @@ import ( ) type CreateAdminReq struct { - FirstName string `json:"first_name" example:"John"` - LastName string `json:"last_name" example:"Doe"` - Email string `json:"email" example:"john.doe@example.com"` - PhoneNumber string `json:"phone_number" example:"1234567890"` - Password string `json:"password" example:"password123"` + FirstName string `json:"first_name" example:"John"` + LastName string `json:"last_name" example:"Doe"` + Email string `json:"email" example:"john.doe@example.com"` + PhoneNumber string `json:"phone_number" example:"1234567890"` + Password string `json:"password" example:"password123"` } // CreateAdmin godoc @@ -163,7 +163,7 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error { searchQuery := c.Query("query") searchString := domain.ValidString{ Value: searchQuery, - Valid: searchQuery != "", + // Valid: searchQuery != "", } createdBeforeQuery := c.Query("created_before") @@ -195,15 +195,9 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error { // Value: companyID, // Valid: companyID != 0, // }, - Page: domain.ValidInt{ - Value: c.QueryInt("page", 1) - 1, - Valid: true, - }, - PageSize: domain.ValidInt{ - Value: c.QueryInt("page_size", 10), - Valid: true, - }, - Query: searchString, + Page: int64(c.QueryInt("page", 1) - 1), + PageSize: int64(c.QueryInt("page_size", 10)), + Query: searchString.Value, CreatedBefore: createdBefore, CreatedAfter: createdAfter, } @@ -262,11 +256,11 @@ func (h *Handler) GetAllAdmins(c *fiber.Ctx) error { h.mongoLoggerSvc.Info("admins retrieved successfully", zap.Int("status_code", fiber.StatusOK), zap.Int("count", len(result)), - zap.Int("page", filter.Page.Value+1), + zap.Int("page", int(filter.Page+1)), zap.Time("timestamp", time.Now()), ) - return response.WritePaginatedJSON(c, fiber.StatusOK, "Admins retrieved successfully", result, nil, filter.Page.Value+1, int(total)) + return response.WritePaginatedJSON(c, fiber.StatusOK, "Admins retrieved successfully", result, nil, int(filter.Page+1), int(total)) } // GetAdminByID godoc @@ -318,9 +312,9 @@ func (h *Handler) GetAdminByID(c *fiber.Ctx) error { } type updateAdminReq struct { - FirstName string `json:"first_name" example:"John"` - LastName string `json:"last_name" example:"Doe"` - Suspended bool `json:"suspended" example:"false"` + FirstName string `json:"first_name" example:"John"` + LastName string `json:"last_name" example:"Doe"` + Suspended bool `json:"suspended" example:"false"` } // UpdateAdmin godoc @@ -356,15 +350,9 @@ func (h *Handler) UpdateAdmin(c *fiber.Ctx) error { // } err = h.userSvc.UpdateUser(c.Context(), domain.UpdateUserReq{ - UserID: adminID, - FirstName: domain.ValidString{ - Value: req.FirstName, - Valid: req.FirstName != "", - }, - LastName: domain.ValidString{ - Value: req.LastName, - Valid: req.LastName != "", - }, + UserID: adminID, + FirstName: req.FirstName, + LastName: req.LastName, // OrganizationID: orgID, }) if err != nil { diff --git a/internal/web_server/handlers/transaction_approver.go b/internal/web_server/handlers/transaction_approver.go index c16917e..d3e61ef 100644 --- a/internal/web_server/handlers/transaction_approver.go +++ b/internal/web_server/handlers/transaction_approver.go @@ -335,7 +335,7 @@ func (h *Handler) GetTransactionApproverByID(c *fiber.Ctx) error { CreatedAt: user.CreatedAt, // SuspendedAt: user.SuspendedAt, // Suspended: user.Suspended, - LastLogin: *lastLogin, + LastLogin: *lastLogin, } h.mongoLoggerSvc.Info("admin retrieved successfully", @@ -402,15 +402,9 @@ func (h *Handler) UpdateTransactionApprover(c *fiber.Ctx) error { } updateReq := domain.UpdateUserReq{ - UserID: approverID, - FirstName: domain.ValidString{ - Value: req.FirstName, - Valid: req.FirstName != "", - }, - LastName: domain.ValidString{ - Value: req.LastName, - Valid: req.LastName != "", - }, + UserID: approverID, + FirstName: req.FirstName, + LastName: req.LastName, } err = h.userSvc.UpdateUser(c.Context(), updateReq) @@ -432,4 +426,3 @@ func (h *Handler) UpdateTransactionApprover(c *fiber.Ctx) error { return response.WriteJSON(c, fiber.StatusOK, "Transaction approver updated successfully", nil, nil) } - diff --git a/internal/web_server/handlers/user.go b/internal/web_server/handlers/user.go index 51da857..e66745e 100644 --- a/internal/web_server/handlers/user.go +++ b/internal/web_server/handlers/user.go @@ -77,10 +77,10 @@ func (h *Handler) CheckProfileCompleted(c *fiber.Ctx) error { // @Failure 400 {object} domain.ErrorResponse // @Failure 404 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse -// @Router /api/v1/{tenant_slug}/user [put] +// @Router /api/v1/user [put] func (h *Handler) UpdateUser(c *fiber.Ctx) error { // Extract user ID from context - userIDStr, ok := c.Locals("user_id").(string) + userID, ok := c.Locals("user_id").(int64) if !ok { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ Message: "Invalid user context", @@ -88,13 +88,13 @@ func (h *Handler) UpdateUser(c *fiber.Ctx) error { }) } - userID, err := strconv.ParseInt(userIDStr, 10, 64) - if err != nil || userID <= 0 { - return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ - Message: "Invalid user ID", - Error: "User ID must be a positive integer", - }) - } + // userID, err := strconv.ParseInt(userIDStr, 10, 64) + // if err != nil || userID <= 0 { + // return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ + // Message: "Invalid user ID", + // Error: "User ID must be a positive integer", + // }) + // } // Parse request body var req domain.UpdateUserReq @@ -152,12 +152,11 @@ func (h *Handler) UpdateUser(c *fiber.Ctx) error { // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/{tenant_slug}/user/knowledge-level [put] func (h *Handler) UpdateUserKnowledgeLevel(c *fiber.Ctx) error { - userIDStr := c.Locals("user_id").(string) - userID, err := strconv.ParseInt(userIDStr, 10, 64) - if err != nil || userID <= 0 { + userID, ok := c.Locals("user_id").(int64) + if !ok { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ - Message: "Invalid user ID", - Error: "User ID must be a positive integer", + Message: "Invalid user context", + Error: "User ID not found in request context", }) } @@ -169,7 +168,7 @@ func (h *Handler) UpdateUserKnowledgeLevel(c *fiber.Ctx) error { }) } - err = h.userSvc.UpdateUserKnowledgeLevel(c.Context(), userID, req.KnowledgeLevel) + err := h.userSvc.UpdateUserKnowledgeLevel(c.Context(), userID, req.KnowledgeLevel) if err != nil { if errors.Is(err, authentication.ErrUserNotFound) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ @@ -302,30 +301,30 @@ func (h *Handler) ResendOtp(c *fiber.Ctx) error { // @Failure 400 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse // @Router /api/v1/user/{user_name}/is-unique [get] -func (h *Handler) CheckUserNameUnique(c *fiber.Ctx) error { - userName := c.Params("user_name") - if userName == "" { - return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ - Message: "Invalid user name", - Error: "user_name path parameter cannot be empty", - }) - } +// func (h *Handler) CheckUserNameUnique(c *fiber.Ctx) error { +// userName := c.Params("user_name") +// if userName == "" { +// return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ +// Message: "Invalid user name", +// Error: "user_name path parameter cannot be empty", +// }) +// } - isUnique, err := h.userSvc.IsUserNameUnique(c.Context(), userName) - if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ - Message: "Failed to check user name uniqueness", - Error: err.Error(), - }) - } +// isUnique, err := h.userSvc.IsUserNameUnique(c.Context(), userName) +// if err != nil { +// return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{ +// Message: "Failed to check user name uniqueness", +// Error: err.Error(), +// }) +// } - return c.Status(fiber.StatusOK).JSON(domain.Response{ - Message: "User name uniqueness checked successfully", - Data: map[string]bool{ - "is_unique": isUnique, - }, - }) -} +// return c.Status(fiber.StatusOK).JSON(domain.Response{ +// Message: "User name uniqueness checked successfully", +// Data: map[string]bool{ +// "is_unique": isUnique, +// }, +// }) +// } // CheckUserPending godoc // @Summary Check if user status is pending @@ -333,22 +332,28 @@ func (h *Handler) CheckUserNameUnique(c *fiber.Ctx) error { // @Tags user // @Accept json // @Produce json -// @Param user_name path string true "User Name" +// @Param user_id path string true "User ID" // @Success 200 {object} domain.Response // @Failure 400 {object} domain.ErrorResponse // @Failure 404 {object} domain.ErrorResponse // @Failure 500 {object} domain.ErrorResponse -// @Router /api/v1/{tenant_slug}/user/{user_name}/is-pending [get] +// @Router /api/v1/user/{user_id}/is-pending [get] func (h *Handler) CheckUserPending(c *fiber.Ctx) error { - userName := c.Params("user_name") - if userName == "" { + userID, err := strconv.ParseInt(c.Params("user_id"), 10, 64) + if err != nil { return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ - Message: "Invalid user name", - Error: "User name cannot be empty", + Message: "Invalid user ID", + Error: err.Error(), + }) + } + if userID == 0 { + return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{ + Message: "Invalid user ID", + Error: "User ID cannot be empty", }) } - isPending, err := h.userSvc.IsUserPending(c.Context(), userName) + isPending, err := h.userSvc.IsUserPending(c.Context(), userID) if err != nil { if errors.Is(err, authentication.ErrUserNotFound) { return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{ @@ -390,7 +395,7 @@ func (h *Handler) GetAllUsers(c *fiber.Ctx) error { searchQuery := c.Query("query") searchString := domain.ValidString{ Value: searchQuery, - Valid: searchQuery != "", + // Valid: searchQuery != "", } createdBeforeQuery := c.Query("created_before") @@ -416,16 +421,10 @@ func (h *Handler) GetAllUsers(c *fiber.Ctx) error { } filter := domain.UserFilter{ - Role: c.Query("role"), - Page: domain.ValidInt{ - Value: c.QueryInt("page", 1) - 1, - Valid: true, - }, - PageSize: domain.ValidInt{ - Value: c.QueryInt("page_size", 10), - Valid: true, - }, - Query: searchString, + Role: c.Query("role"), + Page: int64(c.QueryInt("page", 1) - 1), + PageSize: int64(c.QueryInt("page_size", 10)), + Query: searchString.Value, CreatedBefore: createdBefore, CreatedAfter: createdAfter, } @@ -456,10 +455,10 @@ func (h *Handler) GetAllUsers(c *fiber.Ctx) error { result := make([]domain.UserProfileResponse, len(users)) for i, u := range users { result[i] = domain.UserProfileResponse{ - ID: u.ID, - FirstName: u.FirstName, - LastName: u.LastName, - UserName: u.UserName, + ID: u.ID, + FirstName: u.FirstName, + LastName: u.LastName, + // UserName: u.UserName, Email: u.Email, PhoneNumber: u.PhoneNumber, Role: u.Role, @@ -472,7 +471,7 @@ func (h *Handler) GetAllUsers(c *fiber.Ctx) error { LearningGoal: u.LearningGoal, LanguageGoal: u.LanguageGoal, LanguageChallange: u.LanguageChallange, - FavoutiteTopic: u.FavoutiteTopic, + FavouriteTopic: u.FavouriteTopic, EmailVerified: u.EmailVerified, PhoneVerified: u.PhoneVerified, LastLogin: u.LastLogin, @@ -793,9 +792,9 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error { } user := domain.RegisterUserReq{ - FirstName: req.FirstName, - LastName: req.LastName, - UserName: req.UserName, + // FirstName: req.FirstName, + // LastName: req.LastName, + // UserName: req.UserName, Email: req.Email, PhoneNumber: req.PhoneNumber, Password: req.Password, @@ -812,7 +811,7 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error { // LearningGoal: req.LearningGoal, // LanguageGoal: req.LanguageGoal, // LanguageChallange: req.LanguageChallange, - // FavoutiteTopic: req.FavoutiteTopic, + // FavouriteTopic: req.FavouriteTopic, } medium, err := getMedium(req.Email, req.PhoneNumber) @@ -855,9 +854,9 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error { func MapRegisterReqToUser(req domain.RegisterUserReq) domain.User { return domain.User{ - FirstName: req.FirstName, - LastName: req.LastName, - UserName: req.UserName, + // FirstName: req.FirstName, + // LastName: req.LastName, + // UserName: req.UserName, Email: req.Email, PhoneNumber: req.PhoneNumber, Password: []byte(req.Password), // or hashed password @@ -873,7 +872,7 @@ func MapRegisterReqToUser(req domain.RegisterUserReq) domain.User { // LearningGoal: req.LearningGoal, // LanguageGoal: req.LanguageGoal, // LanguageChallange: req.LanguageChallange, - // FavoutiteTopic: req.FavoutiteTopic, + // FavouriteTopic: req.FavouriteTopic, } } @@ -931,7 +930,7 @@ func (h *Handler) SendResetCode(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided") } - if err := h.userSvc.SendResetCode(c.Context(), "", medium, sentTo, domain.AfroMessage); err != nil { + if err := h.userSvc.SendResetCode(c.Context(), 0, medium, sentTo, domain.AfroMessage); err != nil { h.mongoLoggerSvc.Error("Failed to send reset code", zap.String("medium", string(medium)), zap.String("sentTo", string(sentTo)), @@ -998,7 +997,7 @@ func (h *Handler) SendTenantResetCode(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, "Email or PhoneNumber must be provided") } - if err := h.userSvc.SendResetCode(c.Context(), "", medium, sentTo, domain.AfroMessage); err != nil { + if err := h.userSvc.SendResetCode(c.Context(), 0, medium, sentTo, domain.AfroMessage); err != nil { h.mongoLoggerSvc.Error("Failed to send reset code", zap.String("medium", string(medium)), zap.String("sentTo", string(sentTo)), @@ -1013,7 +1012,7 @@ func (h *Handler) SendTenantResetCode(c *fiber.Ctx) error { } type ResetPasswordReq struct { - UserName string `json:"user_name" validate:"required" example:"johndoe"` + UserID int64 `json:"user_name" validate:"required" example:"johndoe"` Password string `json:"password" validate:"required,min=8" example:"newpassword123"` Otp string `json:"otp" validate:"required" example:"123456"` } @@ -1072,7 +1071,7 @@ func (h *Handler) ResetPassword(c *fiber.Ctx) error { // } resetReq := domain.ResetPasswordReq{ - UserName: req.UserName, + UserID: req.UserID, Password: req.Password, OtpCode: req.Otp, } @@ -1139,7 +1138,7 @@ func (h *Handler) ResetTenantPassword(c *fiber.Ctx) error { // } resetReq := domain.ResetPasswordReq{ - UserName: req.UserName, + UserID: req.UserID, Password: req.Password, OtpCode: req.Otp, } @@ -1207,10 +1206,11 @@ func (h *Handler) GetUserProfile(c *fiber.Ctx) error { } res := domain.UserProfileResponse{ - ID: user.ID, - FirstName: user.FirstName, - LastName: user.LastName, - UserName: user.UserName, + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + // UserName: user.UserName, + Occupation: user.Occupation, Email: user.Email, PhoneNumber: user.PhoneNumber, Role: user.Role, @@ -1296,10 +1296,10 @@ func (h *Handler) AdminProfile(c *fiber.Ctx) error { lastLogin = &user.CreatedAt } res := domain.UserProfileResponse{ - ID: user.ID, - FirstName: user.FirstName, - LastName: user.LastName, - UserName: user.UserName, + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + // UserName: user.UserName, Email: user.Email, PhoneNumber: user.PhoneNumber, Role: user.Role, @@ -1418,10 +1418,10 @@ func (h *Handler) SearchUserByNameOrPhone(c *fiber.Ctx) error { // } res = append(res, domain.UserProfileResponse{ - ID: user.ID, - FirstName: user.FirstName, - LastName: user.LastName, - UserName: user.UserName, + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + // UserName: user.UserName, Email: user.Email, PhoneNumber: user.PhoneNumber, Role: user.Role, @@ -1500,10 +1500,10 @@ func (h *Handler) GetUserByID(c *fiber.Ctx) error { // } res := domain.UserProfileResponse{ - ID: user.ID, - FirstName: user.FirstName, - LastName: user.LastName, - UserName: user.UserName, + ID: user.ID, + FirstName: user.FirstName, + LastName: user.LastName, + // UserName: user.UserName, Email: user.Email, PhoneNumber: user.PhoneNumber, Role: user.Role, diff --git a/internal/web_server/routes.go b/internal/web_server/routes.go index f1089bc..fbe1592 100644 --- a/internal/web_server/routes.go +++ b/internal/web_server/routes.go @@ -198,7 +198,7 @@ func (a *App) initAppRoutes() { groupV1.Get("/users", a.authMiddleware, h.GetAllUsers) groupV1.Put("/user", a.authMiddleware, h.UpdateUser) groupV1.Put("/user/knowledge-level", h.UpdateUserKnowledgeLevel) - groupV1.Get("/user/:user_name/is-unique", h.CheckUserNameUnique) + // groupV1.Get("/user/:user_name/is-unique", h.CheckUserNameUnique) groupV1.Get("/user/:user_name/is-pending", h.CheckUserPending) groupV1.Post("/user/resetPassword", h.ResetPassword) groupV1.Post("/user/sendResetCode", h.SendResetCode)