schema adjustment and profile management fixes
This commit is contained in:
parent
8ed0a5f1c6
commit
d94774c138
|
|
@ -1,40 +1,3 @@
|
|||
CREATE TABLE assessment_questions (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
question_type VARCHAR(50) NOT NULL, -- MULTIPLE_CHOICE, TRUE_FALSE, SHORT_ANSWER
|
||||
difficulty_level VARCHAR(50) NOT NULL, -- BEGINNER, INTERMEDIATE, ADVANCED
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE TABLE assessment_question_options (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
question_id BIGINT NOT NULL REFERENCES assessment_questions(id) ON DELETE CASCADE,
|
||||
option_text TEXT NOT NULL,
|
||||
is_correct BOOLEAN NOT NULL DEFAULT FALSE
|
||||
);
|
||||
|
||||
CREATE TABLE assessment_attempts (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
total_questions INT NOT NULL,
|
||||
correct_answers INT NOT NULL,
|
||||
score_percentage NUMERIC(5,2) NOT NULL,
|
||||
knowledge_level VARCHAR(50) NOT NULL, -- BEGINNER, INTERMEDIATE, ADVANCED
|
||||
completed_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE assessment_answers (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
attempt_id BIGINT NOT NULL REFERENCES assessment_attempts(id) ON DELETE CASCADE,
|
||||
question_id BIGINT NOT NULL REFERENCES assessment_questions(id),
|
||||
selected_option_id BIGINT REFERENCES assessment_question_options(id),
|
||||
short_answer TEXT,
|
||||
is_correct BOOLEAN NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
first_name VARCHAR(255) NOT NULL,
|
||||
|
|
@ -73,6 +36,43 @@ CREATE TABLE IF NOT EXISTS users (
|
|||
CHECK (email IS NOT NULL OR phone_number IS NOT NULL)
|
||||
);
|
||||
|
||||
CREATE TABLE assessment_questions (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
question_type VARCHAR(50) NOT NULL, -- MULTIPLE_CHOICE, TRUE_FALSE, SHORT_ANSWER
|
||||
difficulty_level VARCHAR(50) NOT NULL, -- BEGINNER, INTERMEDIATE, ADVANCED
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE TABLE assessment_question_options (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
question_id BIGINT NOT NULL REFERENCES assessment_questions(id) ON DELETE CASCADE,
|
||||
option_text TEXT NOT NULL,
|
||||
is_correct BOOLEAN NOT NULL DEFAULT FALSE
|
||||
);
|
||||
|
||||
CREATE TABLE assessment_attempts (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
total_questions INT NOT NULL,
|
||||
correct_answers INT NOT NULL,
|
||||
score_percentage NUMERIC(5,2) NOT NULL,
|
||||
knowledge_level VARCHAR(50) NOT NULL, -- BEGINNER, INTERMEDIATE, ADVANCED
|
||||
completed_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE assessment_answers (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
attempt_id BIGINT NOT NULL REFERENCES assessment_attempts(id) ON DELETE CASCADE,
|
||||
question_id BIGINT NOT NULL REFERENCES assessment_questions(id),
|
||||
selected_option_id BIGINT REFERENCES assessment_question_options(id),
|
||||
short_answer TEXT,
|
||||
is_correct BOOLEAN NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE refresh_tokens (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
|
|
|
|||
|
|
@ -5,13 +5,18 @@ SET
|
|||
used = FALSE,
|
||||
used_at = NULL,
|
||||
expires_at = $3
|
||||
WHERE
|
||||
user_name = $1
|
||||
AND expires_at <= NOW();
|
||||
WHERE user_name = $1;
|
||||
|
||||
-- name: CreateOtp :exec
|
||||
INSERT INTO otps (user_name, sent_to, medium, otp_for, otp, used, created_at, expires_at)
|
||||
VALUES ($1, $2, $3, $4, $5, FALSE, $6, $7);
|
||||
INSERT INTO otps (
|
||||
user_name,
|
||||
sent_to,
|
||||
medium,
|
||||
otp_for,
|
||||
otp,
|
||||
expires_at
|
||||
)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -210,30 +210,29 @@ WHERE (
|
|||
-- name: UpdateUser :exec
|
||||
UPDATE users
|
||||
SET
|
||||
first_name = $1,
|
||||
last_name = $2,
|
||||
user_name = $3,
|
||||
age = $4,
|
||||
education_level = $5,
|
||||
country = $6,
|
||||
region = $7,
|
||||
|
||||
nick_name = $8,
|
||||
occupation = $9,
|
||||
learning_goal = $10,
|
||||
language_goal = $11,
|
||||
language_challange = $12,
|
||||
favoutite_topic = $13,
|
||||
|
||||
initial_assessment_completed = $14,
|
||||
email_verified = $15,
|
||||
phone_verified = $16,
|
||||
status = $17,
|
||||
profile_completed = $18,
|
||||
profile_picture_url = $19,
|
||||
preferred_language = $20,
|
||||
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),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $21;
|
||||
WHERE id = $22;
|
||||
|
||||
-- name: DeleteUser :exec
|
||||
DELETE FROM users
|
||||
|
|
|
|||
|
|
@ -21,6 +21,23 @@ services:
|
|||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./exports:/exports
|
||||
|
||||
pgadmin:
|
||||
container_name: yimaru-pgadmin
|
||||
image: dpage/pgadmin4:latest
|
||||
restart: always
|
||||
ports:
|
||||
- "5050:80"
|
||||
environment:
|
||||
PGADMIN_DEFAULT_EMAIL: admin@local.dev
|
||||
PGADMIN_DEFAULT_PASSWORD: admin
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- app
|
||||
volumes:
|
||||
- pgadmin_data:/var/lib/pgadmin
|
||||
|
||||
mongo:
|
||||
container_name: yimaru-mongo
|
||||
image: mongo:7.0.11
|
||||
|
|
@ -53,21 +70,11 @@ services:
|
|||
"/migrations",
|
||||
"-database",
|
||||
"postgresql://root:secret@postgres:5432/gh?sslmode=disable",
|
||||
"up",
|
||||
"up"
|
||||
]
|
||||
networks:
|
||||
- app
|
||||
# redis:
|
||||
# image: redis:7-alpine
|
||||
# ports:
|
||||
# - "6379:6379"
|
||||
# networks:
|
||||
# - app
|
||||
# healthcheck:
|
||||
# test: ["CMD", "redis-cli", "ping"]
|
||||
# interval: 10s
|
||||
# timeout: 5s
|
||||
# retries: 5
|
||||
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
|
|
@ -84,8 +91,6 @@ services:
|
|||
networks:
|
||||
- app
|
||||
command: ["/app/bin/web"]
|
||||
# volumes:
|
||||
# - "C:/Users/User/Desktop:/host-desktop"
|
||||
|
||||
test:
|
||||
build:
|
||||
|
|
@ -105,3 +110,4 @@ networks:
|
|||
volumes:
|
||||
postgres_data:
|
||||
mongo_data:
|
||||
pgadmin_data:
|
||||
|
|
|
|||
|
|
@ -12,8 +12,15 @@ import (
|
|||
)
|
||||
|
||||
const CreateOtp = `-- name: CreateOtp :exec
|
||||
INSERT INTO otps (user_name, sent_to, medium, otp_for, otp, used, created_at, expires_at)
|
||||
VALUES ($1, $2, $3, $4, $5, FALSE, $6, $7)
|
||||
INSERT INTO otps (
|
||||
user_name,
|
||||
sent_to,
|
||||
medium,
|
||||
otp_for,
|
||||
otp,
|
||||
expires_at
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
`
|
||||
|
||||
type CreateOtpParams struct {
|
||||
|
|
@ -22,7 +29,6 @@ type CreateOtpParams struct {
|
|||
Medium string `json:"medium"`
|
||||
OtpFor string `json:"otp_for"`
|
||||
Otp string `json:"otp"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
ExpiresAt pgtype.Timestamptz `json:"expires_at"`
|
||||
}
|
||||
|
||||
|
|
@ -33,7 +39,6 @@ func (q *Queries) CreateOtp(ctx context.Context, arg CreateOtpParams) error {
|
|||
arg.Medium,
|
||||
arg.OtpFor,
|
||||
arg.Otp,
|
||||
arg.CreatedAt,
|
||||
arg.ExpiresAt,
|
||||
)
|
||||
return err
|
||||
|
|
@ -100,9 +105,7 @@ SET
|
|||
used = FALSE,
|
||||
used_at = NULL,
|
||||
expires_at = $3
|
||||
WHERE
|
||||
user_name = $1
|
||||
AND expires_at <= NOW()
|
||||
WHERE user_name = $1
|
||||
`
|
||||
|
||||
type UpdateExpiredOtpParams struct {
|
||||
|
|
|
|||
|
|
@ -842,36 +842,36 @@ func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams)
|
|||
const UpdateUser = `-- name: UpdateUser :exec
|
||||
UPDATE users
|
||||
SET
|
||||
first_name = $1,
|
||||
last_name = $2,
|
||||
user_name = $3,
|
||||
age = $4,
|
||||
education_level = $5,
|
||||
country = $6,
|
||||
region = $7,
|
||||
|
||||
nick_name = $8,
|
||||
occupation = $9,
|
||||
learning_goal = $10,
|
||||
language_goal = $11,
|
||||
language_challange = $12,
|
||||
favoutite_topic = $13,
|
||||
|
||||
initial_assessment_completed = $14,
|
||||
email_verified = $15,
|
||||
phone_verified = $16,
|
||||
status = $17,
|
||||
profile_completed = $18,
|
||||
profile_picture_url = $19,
|
||||
preferred_language = $20,
|
||||
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),
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $21
|
||||
WHERE id = $22
|
||||
`
|
||||
|
||||
type UpdateUserParams struct {
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
UserName string `json:"user_name"`
|
||||
KnowledgeLevel pgtype.Text `json:"knowledge_level"`
|
||||
Age pgtype.Int4 `json:"age"`
|
||||
EducationLevel pgtype.Text `json:"education_level"`
|
||||
Country pgtype.Text `json:"country"`
|
||||
|
|
@ -897,6 +897,7 @@ func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error {
|
|||
arg.FirstName,
|
||||
arg.LastName,
|
||||
arg.UserName,
|
||||
arg.KnowledgeLevel,
|
||||
arg.Age,
|
||||
arg.EducationLevel,
|
||||
arg.Country,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ type User struct {
|
|||
Region string
|
||||
|
||||
// Profile fields
|
||||
KnowledgeLevel string
|
||||
initial_assessment_completed bool
|
||||
NickName string
|
||||
Occupation string
|
||||
|
|
@ -185,6 +186,7 @@ type UpdateUserReq struct {
|
|||
Region ValidString
|
||||
|
||||
// Profile fields
|
||||
KnowledgeLevel ValidString
|
||||
NickName ValidString
|
||||
Occupation ValidString
|
||||
LearningGoal ValidString
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
)
|
||||
|
||||
type UserStore interface {
|
||||
UpdateUserStatus(ctx context.Context, user domain.UpdateUserReq) error
|
||||
GetCorrectOptionForQuestion(
|
||||
ctx context.Context,
|
||||
questionID int64,
|
||||
|
|
@ -67,7 +68,7 @@ type EmailGateway interface {
|
|||
SendEmailOTP(ctx context.Context, email string, otp string) error
|
||||
}
|
||||
type OtpStore interface {
|
||||
UpdateExpiredOtp(ctx context.Context, otp, userName string) error
|
||||
UpdateOtp(ctx context.Context, otp, userName string) 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)
|
||||
|
|
|
|||
|
|
@ -21,11 +21,19 @@ func NewTokenStore(s *Store) ports.TokenStore {
|
|||
|
||||
// CreateRefreshToken inserts a new refresh token into the database
|
||||
func (s *Store) CreateRefreshToken(ctx context.Context, rt domain.RefreshToken) error {
|
||||
rt.ExpiresAt = time.Now().Add(10 * time.Minute)
|
||||
|
||||
return s.queries.CreateRefreshToken(ctx, dbgen.CreateRefreshTokenParams{
|
||||
UserID: rt.UserID,
|
||||
Token: rt.Token,
|
||||
ExpiresAt: pgtype.Timestamptz{Time: rt.ExpiresAt},
|
||||
CreatedAt: pgtype.Timestamptz{Time: rt.CreatedAt},
|
||||
ExpiresAt: pgtype.Timestamptz{
|
||||
Time: rt.ExpiresAt,
|
||||
Valid: true,
|
||||
},
|
||||
CreatedAt: pgtype.Timestamptz{
|
||||
Time: time.Now(),
|
||||
Valid: true,
|
||||
},
|
||||
Revoked: rt.Revoked,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import (
|
|||
// Interface for creating new otp store
|
||||
func NewOTPStore(s *Store) ports.OtpStore { return s }
|
||||
|
||||
func (s *Store) UpdateExpiredOtp(ctx context.Context, otp, userName string) error {
|
||||
func (s *Store) UpdateOtp(ctx context.Context, otp, userName string) error {
|
||||
return s.queries.UpdateExpiredOtp(ctx, dbgen.UpdateExpiredOtpParams{
|
||||
UserName: userName,
|
||||
Otp: otp,
|
||||
|
|
@ -29,6 +29,7 @@ func (s *Store) UpdateExpiredOtp(ctx context.Context, otp, userName string) erro
|
|||
|
||||
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),
|
||||
|
|
@ -37,12 +38,9 @@ func (s *Store) CreateOtp(ctx context.Context, otp domain.Otp) error {
|
|||
Time: otp.ExpiresAt,
|
||||
Valid: true,
|
||||
},
|
||||
CreatedAt: pgtype.Timestamptz{
|
||||
Time: otp.CreatedAt,
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Store) GetOtp(ctx context.Context, userName string) (domain.Otp, error) {
|
||||
row, err := s.queries.GetOtp(ctx, userName)
|
||||
if err != nil {
|
||||
|
|
@ -54,6 +52,7 @@ func (s *Store) GetOtp(ctx context.Context, userName string) (domain.Otp, error)
|
|||
}
|
||||
return domain.Otp{
|
||||
ID: row.ID,
|
||||
UserName: row.UserName,
|
||||
SentTo: row.SentTo,
|
||||
Medium: domain.OtpMedium(row.Medium),
|
||||
For: domain.OtpFor(row.OtpFor),
|
||||
|
|
|
|||
|
|
@ -42,6 +42,13 @@ func (s *Store) IsUserNameUnique(ctx context.Context, userName string) (bool, er
|
|||
return isUnique, nil
|
||||
}
|
||||
|
||||
func (s *Store) UpdateUserStatus(ctx context.Context, user domain.UpdateUserReq) error {
|
||||
return s.queries.UpdateUserStatus(ctx, dbgen.UpdateUserStatusParams{
|
||||
Status: user.Status.Value,
|
||||
ID: user.UserID,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Store) CreateUserWithoutOtp(
|
||||
ctx context.Context,
|
||||
user domain.User,
|
||||
|
|
|
|||
|
|
@ -48,14 +48,14 @@ func (s *Service) ResendOtp(
|
|||
return fmt.Errorf("invalid otp medium: %s", otp.Medium)
|
||||
}
|
||||
|
||||
if err := s.otpStore.UpdateExpiredOtp(ctx, otp.Otp, userName); err != nil {
|
||||
if err := s.otpStore.UpdateOtp(ctx, otpCode, userName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium, provider domain.SMSProvider) error {
|
||||
func (s *Service) SendOtp(ctx context.Context, userName string, 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)
|
||||
|
|
@ -82,6 +82,7 @@ func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpF
|
|||
}
|
||||
|
||||
otp := domain.Otp{
|
||||
UserName: userName,
|
||||
SentTo: sentTo,
|
||||
Medium: medium,
|
||||
For: otpFor,
|
||||
|
|
|
|||
|
|
@ -36,6 +36,21 @@ func (s *Service) VerifyOtp(ctx context.Context, userName string, otpCode string
|
|||
return err
|
||||
}
|
||||
|
||||
user, err := s.userStore.GetUserByUserName(ctx, userName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newUser := domain.UpdateUserReq{
|
||||
UserID: user.ID,
|
||||
Status: domain.ValidString{
|
||||
Value: string(domain.UserStatusActive),
|
||||
Valid: true,
|
||||
},
|
||||
}
|
||||
|
||||
s.userStore.UpdateUserStatus(ctx, newUser)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -59,7 +74,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, "", sentTo, domain.OtpRegister, medium, provider)
|
||||
}
|
||||
|
||||
func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) {
|
||||
|
|
@ -94,7 +109,7 @@ func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterU
|
|||
PhoneNumber: registerReq.PhoneNumber,
|
||||
Password: hashedPassword,
|
||||
Role: domain.RoleStudent,
|
||||
EmailVerified: false, // verification pending via OTP
|
||||
EmailVerified: false,
|
||||
PhoneVerified: false,
|
||||
EducationLevel: registerReq.EducationLevel,
|
||||
Age: registerReq.Age,
|
||||
|
|
@ -103,6 +118,16 @@ func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterU
|
|||
Status: domain.UserStatusPending,
|
||||
ProfileCompleted: false,
|
||||
PreferredLanguage: registerReq.PreferredLanguage,
|
||||
|
||||
// Optional fields
|
||||
NickName: registerReq.NickName,
|
||||
Occupation: registerReq.Occupation,
|
||||
LearningGoal: registerReq.LearningGoal,
|
||||
LanguageGoal: registerReq.LanguageGoal,
|
||||
LanguageChallange: registerReq.LanguageChallange,
|
||||
FavoutiteTopic: registerReq.FavoutiteTopic,
|
||||
// ProfilePictureURL: registerReq.ProfilePictureURL,
|
||||
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
|
|
@ -115,7 +140,7 @@ func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterU
|
|||
}
|
||||
|
||||
// Send OTP to the user (email/SMS)
|
||||
if err := s.SendOtp(ctx, sentTo, domain.OtpRegister, registerReq.OtpMedium, domain.TwilioSms); err != nil {
|
||||
if err := s.SendOtp(ctx, registerReq.UserName, sentTo, domain.OtpRegister, registerReq.OtpMedium, domain.TwilioSms); err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, sentTo string, provider domain.SMSProvider) error {
|
||||
func (s *Service) SendResetCode(ctx context.Context, userName string, medium domain.OtpMedium, sentTo string, provider domain.SMSProvider) error {
|
||||
|
||||
var err error
|
||||
// check if user exists
|
||||
|
|
@ -22,7 +22,7 @@ func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, se
|
|||
return err
|
||||
}
|
||||
|
||||
return s.SendOtp(ctx, sentTo, domain.OtpReset, medium, provider)
|
||||
return s.SendOtp(ctx, userName, sentTo, domain.OtpReset, medium, provider)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,16 +31,27 @@ func (s *Service) SearchUserByNameOrPhone(ctx context.Context, searchString stri
|
|||
return s.userStore.SearchUserByNameOrPhone(ctx, searchString, roleStr)
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -39,11 +39,6 @@ type loginUserRes struct {
|
|||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/v1/{tenant_slug}/user-login [post]
|
||||
func (h *Handler) LoginUser(c *fiber.Ctx) error {
|
||||
// OrganizationID := c.Locals("company_id").(domain.ValidInt64)
|
||||
// if !OrganizationID.Valid {
|
||||
// h.BadRequestLogger().Error("invalid company id")
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
|
||||
// }
|
||||
var req loginUserReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
h.mongoLoggerSvc.Info("Failed to parse LoginUser request",
|
||||
|
|
@ -51,7 +46,10 @@ func (h *Handler) LoginUser(c *fiber.Ctx) error {
|
|||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid request body"+err.Error())
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to login",
|
||||
Error: "Invalid request body: " + err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
if valErrs, ok := h.validator.Validate(c, req); !ok {
|
||||
|
|
@ -59,11 +57,13 @@ func (h *Handler) LoginUser(c *fiber.Ctx) error {
|
|||
for field, msg := range valErrs {
|
||||
errMsg += fmt.Sprintf("%s: %s; ", field, msg)
|
||||
}
|
||||
return fiber.NewError(fiber.StatusBadRequest, errMsg)
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to login",
|
||||
Error: errMsg,
|
||||
})
|
||||
}
|
||||
|
||||
successRes, err := h.authSvc.Login(c.Context(), req.UserName, req.Password)
|
||||
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, authentication.ErrInvalidPassword), errors.Is(err, authentication.ErrUserNotFound):
|
||||
|
|
@ -73,7 +73,10 @@ func (h *Handler) LoginUser(c *fiber.Ctx) error {
|
|||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Invalid credentials")
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to login",
|
||||
Error: fmt.Sprintf("Invalid credentials: %v", err),
|
||||
})
|
||||
case errors.Is(err, authentication.ErrUserSuspended):
|
||||
h.mongoLoggerSvc.Info("Login attempt failed: User login has been locked",
|
||||
zap.Int("status_code", fiber.StatusUnauthorized),
|
||||
|
|
@ -81,14 +84,20 @@ func (h *Handler) LoginUser(c *fiber.Ctx) error {
|
|||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "User login has been locked")
|
||||
return c.Status(fiber.StatusUnauthorized).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to login",
|
||||
Error: fmt.Sprintf("User login has been locked: %v", err),
|
||||
})
|
||||
default:
|
||||
h.mongoLoggerSvc.Error("Login failed",
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Internal server error")
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to login",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -97,10 +106,12 @@ func (h *Handler) LoginUser(c *fiber.Ctx) error {
|
|||
zap.Int("status_code", fiber.StatusForbidden),
|
||||
zap.String("role", string(successRes.Role)),
|
||||
zap.String("user_name", req.UserName),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusForbidden, "Only users are allowed to login ")
|
||||
return c.Status(fiber.StatusForbidden).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to login",
|
||||
Error: "Only users are allowed to login",
|
||||
})
|
||||
}
|
||||
|
||||
accessToken, err := jwtutil.CreateJwt(
|
||||
|
|
@ -116,7 +127,10 @@ func (h *Handler) LoginUser(c *fiber.Ctx) error {
|
|||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to generate access token")
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to login",
|
||||
Error: "Failed to generate access token",
|
||||
})
|
||||
}
|
||||
|
||||
res := loginUserRes{
|
||||
|
|
@ -132,7 +146,10 @@ func (h *Handler) LoginUser(c *fiber.Ctx) error {
|
|||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Login successful", res, nil)
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Message: "Login successful",
|
||||
Data: res,
|
||||
})
|
||||
}
|
||||
|
||||
// loginAdminReq represents the request body for the LoginAdmin endpoint.
|
||||
|
|
|
|||
|
|
@ -13,6 +13,79 @@ import (
|
|||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// UpdateUser godoc
|
||||
// @Summary Update user profile
|
||||
// @Description Updates user profile information (partial updates supported)
|
||||
// @Tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param user_id path int true "User ID"
|
||||
// @Param body body domain.UpdateUserReq true "Update user payload"
|
||||
// @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 [put]
|
||||
func (h *Handler) UpdateUser(c *fiber.Ctx) error {
|
||||
// Extract user ID from context
|
||||
userIDStr, ok := c.Locals("user_id").(string)
|
||||
if !ok {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Invalid user context",
|
||||
Error: "User ID not found in request context",
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
Message: "Invalid request body",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
// Enforce user identity
|
||||
req.UserID = userID
|
||||
|
||||
// Optional: lightweight validation (example)
|
||||
// if req.Status.IsSet() {
|
||||
// if !domain.(req.Status.Value) {
|
||||
// return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
|
||||
// Message: "Invalid status value",
|
||||
// Error: "Unsupported user status",
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// Call service
|
||||
if err := h.userSvc.UpdateUser(c.Context(), req); err != nil {
|
||||
if errors.Is(err, authentication.ErrUserNotFound) {
|
||||
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
|
||||
Message: "User not found",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
|
||||
Message: "Failed to update user",
|
||||
Error: err.Error(),
|
||||
})
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Message: "User updated successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateUserKnowledgeLevel godoc
|
||||
// @Summary Update user's knowledge level
|
||||
// @Description Updates the knowledge level of the specified user after initial assessment
|
||||
|
|
@ -158,6 +231,8 @@ func (h *Handler) ResendOtp(c *fiber.Ctx) error {
|
|||
|
||||
return c.Status(fiber.StatusOK).JSON(domain.Response{
|
||||
Message: "OTP resent successfully",
|
||||
Success: true,
|
||||
StatusCode: fiber.StatusOK,
|
||||
Data: nil,
|
||||
})
|
||||
}
|
||||
|
|
@ -560,6 +635,13 @@ func (h *Handler) RegisterUser(c *fiber.Ctx) error {
|
|||
Country: req.Country,
|
||||
Region: req.Region,
|
||||
PreferredLanguage: req.PreferredLanguage,
|
||||
|
||||
NickName: req.NickName,
|
||||
Occupation: req.Occupation,
|
||||
LearningGoal: req.LearningGoal,
|
||||
LanguageGoal: req.LanguageGoal,
|
||||
LanguageChallange: req.LanguageChallange,
|
||||
FavoutiteTopic: req.FavoutiteTopic,
|
||||
}
|
||||
|
||||
medium, err := getMedium(req.Email, req.PhoneNumber)
|
||||
|
|
@ -600,6 +682,30 @@ 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,
|
||||
Email: req.Email,
|
||||
PhoneNumber: req.PhoneNumber,
|
||||
Password: []byte(req.Password), // or hashed password
|
||||
Role: domain.Role(req.Role),
|
||||
Age: req.Age,
|
||||
EducationLevel: req.EducationLevel,
|
||||
Country: req.Country,
|
||||
Region: req.Region,
|
||||
PreferredLanguage: req.PreferredLanguage,
|
||||
|
||||
NickName: req.NickName,
|
||||
Occupation: req.Occupation,
|
||||
LearningGoal: req.LearningGoal,
|
||||
LanguageGoal: req.LanguageGoal,
|
||||
LanguageChallange: req.LanguageChallange,
|
||||
FavoutiteTopic: req.FavoutiteTopic,
|
||||
}
|
||||
}
|
||||
|
||||
type ResetCodeReq struct {
|
||||
Email string `json:"email" example:"john.doe@example.com"`
|
||||
PhoneNumber string `json:"phone_number" validate:"required_without=Email" example:"1234567890"`
|
||||
|
|
@ -654,7 +760,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(), "", medium, sentTo, domain.AfroMessage); err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to send reset code",
|
||||
zap.String("medium", string(medium)),
|
||||
zap.String("sentTo", string(sentTo)),
|
||||
|
|
@ -721,7 +827,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(), "", medium, sentTo, domain.AfroMessage); err != nil {
|
||||
h.mongoLoggerSvc.Error("Failed to send reset code",
|
||||
zap.String("medium", string(medium)),
|
||||
zap.String("sentTo", string(sentTo)),
|
||||
|
|
|
|||
|
|
@ -70,24 +70,24 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
|
|||
|
||||
}
|
||||
// Asserting to make sure that only the super admin can have a nil company ID
|
||||
if claim.Role != domain.RoleSuperAdmin && !claim.CompanyID.Valid {
|
||||
a.mongoLoggerSvc.Error("Company Role without Company ID",
|
||||
zap.Int64("userID", claim.UserId),
|
||||
zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
zap.Error(err),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Company Role without Company ID")
|
||||
}
|
||||
// if claim.Role != domain.RoleSuperAdmin && !claim.CompanyID.Valid {
|
||||
// a.mongoLoggerSvc.Error("Company Role without Company ID",
|
||||
// zap.Int64("userID", claim.UserId),
|
||||
// zap.Int("status_code", fiber.StatusInternalServerError),
|
||||
// zap.Error(err),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusInternalServerError, "Company Role without Company ID")
|
||||
// }
|
||||
c.Locals("user_id", claim.UserId)
|
||||
c.Locals("role", claim.Role)
|
||||
c.Locals("company_id", domain.ValidInt64{
|
||||
Value: claim.CompanyID.Value,
|
||||
Valid: claim.CompanyID.Valid,
|
||||
})
|
||||
// c.Locals("company_id", domain.ValidInt64{
|
||||
// Value: claim.CompanyID.Value,
|
||||
// Valid: claim.CompanyID.Valid,
|
||||
// })
|
||||
c.Locals("refresh_token", refreshToken)
|
||||
|
||||
var branchID domain.ValidInt64
|
||||
// var branchID domain.ValidInt64
|
||||
|
||||
if claim.Role == domain.RoleAdmin {
|
||||
// branch, err := a.branchSvc.GetBranchByCashier(c.Context(), claim.UserId)
|
||||
|
|
@ -107,7 +107,7 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
|
|||
|
||||
}
|
||||
|
||||
c.Locals("branch_id", branchID)
|
||||
// c.Locals("branch_id", branchID)
|
||||
return c.Next()
|
||||
}
|
||||
|
||||
|
|
@ -126,20 +126,20 @@ func (a *App) SuperAdminOnly(c *fiber.Ctx) error {
|
|||
return c.Next()
|
||||
}
|
||||
|
||||
func (a *App) CompanyOnly(c *fiber.Ctx) error {
|
||||
userID := c.Locals("user_id").(int64)
|
||||
userRole := c.Locals("role").(domain.Role)
|
||||
if userRole == domain.RoleStudent {
|
||||
a.mongoLoggerSvc.Warn("Attempt to access restricted CompanyOnly route",
|
||||
zap.Int64("userID", userID),
|
||||
zap.String("role", string(userRole)),
|
||||
zap.Int("status_code", fiber.StatusForbidden),
|
||||
zap.Time("timestamp", time.Now()),
|
||||
)
|
||||
return fiber.NewError(fiber.StatusForbidden, "This route is restricted")
|
||||
}
|
||||
return c.Next()
|
||||
}
|
||||
// func (a *App) CompanyOnly(c *fiber.Ctx) error {
|
||||
// userID := c.Locals("user_id").(int64)
|
||||
// userRole := c.Locals("role").(domain.Role)
|
||||
// if userRole == domain.RoleStudent {
|
||||
// a.mongoLoggerSvc.Warn("Attempt to access restricted CompanyOnly route",
|
||||
// zap.Int64("userID", userID),
|
||||
// zap.String("role", string(userRole)),
|
||||
// zap.Int("status_code", fiber.StatusForbidden),
|
||||
// zap.Time("timestamp", time.Now()),
|
||||
// )
|
||||
// return fiber.NewError(fiber.StatusForbidden, "This route is restricted")
|
||||
// }
|
||||
// return c.Next()
|
||||
// }
|
||||
|
||||
func (a *App) OnlyAdminAndAbove(c *fiber.Ctx) error {
|
||||
userID := c.Locals("user_id").(int64)
|
||||
|
|
|
|||
|
|
@ -42,31 +42,31 @@ func (a *App) initAppRoutes() {
|
|||
|
||||
// Groups
|
||||
groupV1 := a.fiber.Group("/api/v1")
|
||||
tenant := groupV1.Group("/tenant/:tenant_slug", a.TenantMiddleware)
|
||||
tenant.Get("/test", a.authMiddleware, a.authMiddleware, func(c *fiber.Ctx) error {
|
||||
fmt.Printf("\nTest Route %v\n", c.Route().Path)
|
||||
companyID := c.Locals("company_id").(domain.ValidInt64)
|
||||
if !companyID.Valid {
|
||||
h.BadRequestLogger().Error("invalid company id")
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
|
||||
}
|
||||
// tenant := groupV1.Group("/tenant/:tenant_slug", a.TenantMiddleware)
|
||||
// groupV1.Get("/test", a.authMiddleware, a.authMiddleware, func(c *fiber.Ctx) error {
|
||||
// fmt.Printf("\nTest Route %v\n", c.Route().Path)
|
||||
// companyID := c.Locals("company_id").(domain.ValidInt64)
|
||||
// if !companyID.Valid {
|
||||
// h.BadRequestLogger().Error("invalid company id")
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
|
||||
// }
|
||||
|
||||
fmt.Printf("In the tenant auth test \n")
|
||||
return c.JSON(fiber.Map{
|
||||
"message": "Is is fine",
|
||||
})
|
||||
})
|
||||
tenant.Get("/", func(c *fiber.Ctx) error {
|
||||
fmt.Printf("\nTenant Route %v\n", c.Route().Path)
|
||||
companyID := c.Locals("company_id").(domain.ValidInt64)
|
||||
if !companyID.Valid {
|
||||
h.BadRequestLogger().Error("invalid company id")
|
||||
return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
|
||||
}
|
||||
return c.JSON(fiber.Map{
|
||||
"message": "Company Tenant Active",
|
||||
})
|
||||
})
|
||||
// fmt.Printf("In the tenant auth test \n")
|
||||
// return c.JSON(fiber.Map{
|
||||
// "message": "Is is fine",
|
||||
// })
|
||||
// })
|
||||
// groupV1.Get("/", func(c *fiber.Ctx) error {
|
||||
// fmt.Printf("\nTenant Route %v\n", c.Route().Path)
|
||||
// companyID := c.Locals("company_id").(domain.ValidInt64)
|
||||
// if !companyID.Valid {
|
||||
// h.BadRequestLogger().Error("invalid company id")
|
||||
// return fiber.NewError(fiber.StatusBadRequest, "invalid company id")
|
||||
// }
|
||||
// return c.JSON(fiber.Map{
|
||||
// "message": "Company Tenant Active",
|
||||
// })
|
||||
// })
|
||||
|
||||
// Get S
|
||||
groupV1.Get("/tenant", a.authMiddleware, h.GetTenantSlugByToken)
|
||||
|
|
@ -84,7 +84,7 @@ func (a *App) initAppRoutes() {
|
|||
//assessment Routes
|
||||
groupV1.Post("/assessment/questions", h.CreateAssessmentQuestion)
|
||||
groupV1.Get("/assessment/questions", h.GetActiveAssessmentQuestions)
|
||||
tenant.Post("/assessment/submit", a.authMiddleware, h.SubmitAssessment)
|
||||
groupV1.Post("/assessment/submit", a.authMiddleware, h.SubmitAssessment)
|
||||
|
||||
// Course Management Routes
|
||||
groupV1.Post("/course-categories", h.CreateCourseCategory)
|
||||
|
|
@ -114,8 +114,8 @@ func (a *App) initAppRoutes() {
|
|||
groupV1.Post("/levels", h.CreateLevel)
|
||||
|
||||
// Auth Routes
|
||||
tenant.Post("/auth/customer-login", h.LoginUser)
|
||||
tenant.Post("/auth/admin-login", h.LoginAdmin)
|
||||
groupV1.Post("/auth/customer-login", h.LoginUser)
|
||||
groupV1.Post("/auth/admin-login", h.LoginAdmin)
|
||||
groupV1.Post("/auth/super-login", h.LoginSuper)
|
||||
groupV1.Post("/auth/refresh", h.RefreshToken)
|
||||
groupV1.Post("/auth/logout", a.authMiddleware, h.LogOutuser)
|
||||
|
|
@ -156,7 +156,8 @@ func (a *App) initAppRoutes() {
|
|||
// groupV1.Get("/arifpay/payment-methods", a.authMiddleware, h.GetArifpayPaymentMethodsHandler
|
||||
|
||||
// User Routes
|
||||
tenant.Put("/user/knowledge-level", h.UpdateUserKnowledgeLevel)
|
||||
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-pending", h.CheckUserPending)
|
||||
groupV1.Post("/user/resetPassword", h.ResetPassword)
|
||||
|
|
@ -164,15 +165,15 @@ func (a *App) initAppRoutes() {
|
|||
groupV1.Post("/user/verify-otp", h.VerifyOtp)
|
||||
groupV1.Post("/user/resend-otp", h.ResendOtp)
|
||||
|
||||
tenant.Post("/user/resetPassword", h.ResetTenantPassword)
|
||||
tenant.Post("/user/sendResetCode", h.SendTenantResetCode)
|
||||
tenant.Post("/user/register", h.RegisterUser)
|
||||
tenant.Post("/user/sendRegisterCode", h.SendRegisterCode)
|
||||
tenant.Post("/user/checkPhoneEmailExist", h.CheckPhoneEmailExist)
|
||||
groupV1.Post("/user/resetPassword", h.ResetTenantPassword)
|
||||
groupV1.Post("/user/sendResetCode", h.SendTenantResetCode)
|
||||
groupV1.Post("/user/register", h.RegisterUser)
|
||||
groupV1.Post("/user/sendRegisterCode", h.SendRegisterCode)
|
||||
groupV1.Post("/user/checkPhoneEmailExist", h.CheckPhoneEmailExist)
|
||||
|
||||
groupV1.Get("/user/admin-profile", a.authMiddleware, h.AdminProfile)
|
||||
|
||||
tenant.Get("/user/user-profile", a.authMiddleware, h.GetUserProfile)
|
||||
groupV1.Get("/user/user-profile", a.authMiddleware, h.GetUserProfile)
|
||||
|
||||
groupV1.Get("/user/single/:id", a.authMiddleware, h.GetUserByID)
|
||||
groupV1.Delete("/user/delete/:id", a.authMiddleware, h.DeleteUser)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user