fix registration and password reset
This commit is contained in:
parent
d1a33b18dc
commit
ca7aa9d67c
|
|
@ -7,8 +7,11 @@ import (
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||||
customlogger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger"
|
customlogger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger"
|
||||||
|
mockemail "github.com/SamuelTariku/FortuneBet-Backend/internal/mocks/mock_email"
|
||||||
|
mocksms "github.com/SamuelTariku/FortuneBet-Backend/internal/mocks/mock_sms"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||||
httpserver "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server"
|
httpserver "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server"
|
||||||
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||||
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
||||||
|
|
@ -44,10 +47,14 @@ func main() {
|
||||||
store := repository.NewStore(db)
|
store := repository.NewStore(db)
|
||||||
v := customvalidator.NewCustomValidator(validator.New())
|
v := customvalidator.NewCustomValidator(validator.New())
|
||||||
authSvc := authentication.NewService(store, store, cfg.RefreshExpiry)
|
authSvc := authentication.NewService(store, store, cfg.RefreshExpiry)
|
||||||
|
mockSms := mocksms.NewMockSMS()
|
||||||
|
mockemail := mockemail.NewMockEmail()
|
||||||
|
userSvc := user.NewService(store, store, mockSms, mockemail)
|
||||||
app := httpserver.NewApp(cfg.Port, v, authSvc, logger, jwtutil.JwtConfig{
|
app := httpserver.NewApp(cfg.Port, v, authSvc, logger, jwtutil.JwtConfig{
|
||||||
JwtAccessKey: cfg.JwtKey,
|
JwtAccessKey: cfg.JwtKey,
|
||||||
JwtAccessExpiry: cfg.AccessExpiry,
|
JwtAccessExpiry: cfg.AccessExpiry,
|
||||||
})
|
}, userSvc,
|
||||||
|
)
|
||||||
logger.Info("Starting server", "port", cfg.Port)
|
logger.Info("Starting server", "port", cfg.Port)
|
||||||
if err := app.Run(); err != nil {
|
if err := app.Run(); err != nil {
|
||||||
logger.Error("Failed to start server", "error", err)
|
logger.Error("Failed to start server", "error", err)
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,16 @@ CREATE TABLE users (
|
||||||
last_name VARCHAR(255) NOT NULL,
|
last_name VARCHAR(255) NOT NULL,
|
||||||
email VARCHAR(255) UNIQUE ,
|
email VARCHAR(255) UNIQUE ,
|
||||||
phone_number VARCHAR(20) UNIQUE,
|
phone_number VARCHAR(20) UNIQUE,
|
||||||
password BYTEA NOT NULL,
|
|
||||||
role VARCHAR(50) NOT NULL,
|
role VARCHAR(50) NOT NULL,
|
||||||
|
password BYTEA NOT NULL,
|
||||||
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
phone_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
phone_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
updated_at TIMESTAMPTZ ,
|
||||||
|
--
|
||||||
suspended_at TIMESTAMPTZ NULL, -- this can be NULL if the user is not suspended
|
suspended_at TIMESTAMPTZ NULL, -- this can be NULL if the user is not suspended
|
||||||
suspended BOOLEAN NOT NULL DEFAULT FALSE,
|
suspended BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
CHECK (
|
CHECK (email IS NOT NULL OR phone_number IS NOT NULL)
|
||||||
(email IS NOT NULL AND phone_number IS NULL) OR
|
|
||||||
(email IS NULL AND phone_number IS NOT NULL)
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
CREATE TABLE refresh_tokens (
|
CREATE TABLE refresh_tokens (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
|
@ -26,19 +24,38 @@ CREATE TABLE refresh_tokens (
|
||||||
revoked BOOLEAN DEFAULT FALSE NOT NULL,
|
revoked BOOLEAN DEFAULT FALSE NOT NULL,
|
||||||
CONSTRAINT unique_token UNIQUE (token)
|
CONSTRAINT unique_token UNIQUE (token)
|
||||||
);
|
);
|
||||||
|
-----
|
||||||
|
CREATE TABLE otps (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
sent_to VARCHAR(255) NOT NULL,
|
||||||
|
medium VARCHAR(50) NOT NULL,
|
||||||
|
otp_for VARCHAR(50) NOT NULL,
|
||||||
|
otp VARCHAR(10) NOT NULL,
|
||||||
|
used BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
used_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
expires_at TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
----------------------------------------------seed data-------------------------------------------------------------
|
----------------------------------------------seed data-------------------------------------------------------------
|
||||||
-------------------------------------- DO NOT USE IN PRODUCTION-------------------------------------------------
|
-------------------------------------- DO NOT USE IN PRODUCTION-------------------------------------------------
|
||||||
|
|
||||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||||
|
|
||||||
INSERT INTO users (first_name, last_name, email, phone_number, password, role, created_at, updated_at)
|
INSERT INTO users (
|
||||||
VALUES (
|
first_name, last_name, email, phone_number, password, role,
|
||||||
|
email_verified, phone_verified, created_at, updated_at,
|
||||||
|
suspended_at, suspended
|
||||||
|
) VALUES (
|
||||||
'John',
|
'John',
|
||||||
'Doe',
|
'Doe',
|
||||||
'john.doe@example.com',
|
'john.doe@example.com',
|
||||||
'1234567890',
|
NULL,
|
||||||
crypt('password123', gen_salt('bf'))::bytea,
|
crypt('password123', gen_salt('bf'))::bytea,
|
||||||
'user',
|
'customer',
|
||||||
|
TRUE,
|
||||||
|
FALSE,
|
||||||
CURRENT_TIMESTAMP,
|
CURRENT_TIMESTAMP,
|
||||||
CURRENT_TIMESTAMP
|
CURRENT_TIMESTAMP,
|
||||||
|
NULL,
|
||||||
|
FALSE
|
||||||
);
|
);
|
||||||
14
db/query/otp.sql
Normal file
14
db/query/otp.sql
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
-- name: CreateOtp :exec
|
||||||
|
INSERT INTO otps (sent_to, medium, otp_for, otp, used, created_at, expires_at)
|
||||||
|
VALUES ($1, $2, $3, $4, FALSE, CURRENT_TIMESTAMP, $5);
|
||||||
|
|
||||||
|
-- name: GetOtp :one
|
||||||
|
SELECT id, sent_to, medium, otp_for, otp, used, used_at, created_at, expires_at
|
||||||
|
FROM otps
|
||||||
|
WHERE sent_to = $1 AND otp_for = $2 AND medium = $3
|
||||||
|
ORDER BY created_at DESC LIMIT 1;
|
||||||
|
|
||||||
|
-- name: MarkOtpAsUsed :exec
|
||||||
|
UPDATE otps
|
||||||
|
SET used = TRUE, used_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = $1;
|
||||||
|
|
@ -1,16 +1,42 @@
|
||||||
-- name: CreateUser :one
|
-- name: CreateUser :one
|
||||||
INSERT INTO users (first_name, last_name, email, phone_number, password, role, verified)
|
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
INSERT INTO users (first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at)
|
||||||
RETURNING *;
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||||
|
RETURNING id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at;
|
||||||
|
|
||||||
-- name: GetUserByID :one
|
-- name: GetUserByID :one
|
||||||
SELECT * FROM users WHERE id = $1;
|
SELECT *
|
||||||
|
FROM users
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
-- name: GetAllUsers :many
|
-- name: GetAllUsers :many
|
||||||
SELECT * FROM users;
|
SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at
|
||||||
|
FROM users;
|
||||||
|
|
||||||
-- name: UpdateUser :exec
|
-- name: UpdateUser :exec
|
||||||
UPDATE users SET first_name = $2, last_name = $3, email = $4, phone_number = $5, password = $6, role = $7, verified = $8, updated_at = CURRENT_TIMESTAMP WHERE id = $1;
|
UPDATE users
|
||||||
|
SET first_name = $1, last_name = $2, email = $3, phone_number = $4, role = $5, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = $6;
|
||||||
|
|
||||||
-- name: DeleteUser :exec
|
-- name: DeleteUser :exec
|
||||||
DELETE FROM users WHERE id = $1;
|
DELETE FROM users
|
||||||
|
WHERE id = $1;
|
||||||
|
|
||||||
|
-- name: CheckPhoneEmailExist :one
|
||||||
|
SELECT
|
||||||
|
EXISTS (SELECT 1 FROM users WHERE users.phone_number = $1 AND users.phone_number IS NOT NULL) AS phone_exists,
|
||||||
|
EXISTS (SELECT 1 FROM users WHERE users.email = $2 AND users.email IS NOT NULL) AS email_exists;
|
||||||
|
-- name: GetUserByEmail :one
|
||||||
|
SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at
|
||||||
|
FROM users
|
||||||
|
WHERE email = $1;
|
||||||
|
|
||||||
|
-- name: GetUserByPhone :one
|
||||||
|
SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at
|
||||||
|
FROM users
|
||||||
|
WHERE phone_number = $1;
|
||||||
|
|
||||||
|
-- name: UpdatePassword :exec
|
||||||
|
UPDATE users
|
||||||
|
SET password = $1, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE (email = $2 OR phone_number = $3);
|
||||||
447
docs/docs.go
447
docs/docs.go
|
|
@ -44,7 +44,7 @@ const docTemplate = `{
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/httpserver.loginCustomerReq"
|
"$ref": "#/definitions/handlers.loginCustomerReq"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -52,7 +52,7 @@ const docTemplate = `{
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/httpserver.loginCustomerRes"
|
"$ref": "#/definitions/handlers.loginCustomerRes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -96,7 +96,7 @@ const docTemplate = `{
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/httpserver.logoutReq"
|
"$ref": "#/definitions/handlers.logoutReq"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -148,7 +148,7 @@ const docTemplate = `{
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/httpserver.refreshToken"
|
"$ref": "#/definitions/handlers.refreshToken"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -156,7 +156,7 @@ const docTemplate = `{
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/httpserver.loginCustomerRes"
|
"$ref": "#/definitions/handlers.loginCustomerRes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -179,10 +179,439 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/user/checkPhoneEmailExist": {
|
||||||
|
"post": {
|
||||||
|
"description": "Check if phone number or email exist",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Check if phone number or email exist",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Check phone number or email exist",
|
||||||
|
"name": "checkPhoneEmailExist",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.CheckPhoneEmailExistReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.CheckPhoneEmailExistRes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/user/profile": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Get user profile",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Get user profile",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.UserProfileRes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/user/register": {
|
||||||
|
"post": {
|
||||||
|
"description": "Register user",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Register user",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Register user",
|
||||||
|
"name": "registerUser",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.RegisterUserReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/user/resetPassword": {
|
||||||
|
"post": {
|
||||||
|
"description": "Reset password",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Reset password",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Reset password",
|
||||||
|
"name": "resetPassword",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.ResetPasswordReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/user/sendRegisterCode": {
|
||||||
|
"post": {
|
||||||
|
"description": "Send register code",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Send register code",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Send register code",
|
||||||
|
"name": "registerCode",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.RegisterCodeReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/user/sendResetCode": {
|
||||||
|
"post": {
|
||||||
|
"description": "Send reset code",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Send reset code",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Send reset code",
|
||||||
|
"name": "resetCode",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.ResetCodeReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"httpserver.loginCustomerReq": {
|
"domain.Role": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"admin",
|
||||||
|
"customer",
|
||||||
|
"super_admin",
|
||||||
|
"branch_manager",
|
||||||
|
"cashier"
|
||||||
|
],
|
||||||
|
"x-enum-varnames": [
|
||||||
|
"RoleAdmin",
|
||||||
|
"RoleCustomer",
|
||||||
|
"RoleSuperAdmin",
|
||||||
|
"RoleBranchManager",
|
||||||
|
"RoleCashier"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"handlers.CheckPhoneEmailExistReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "john.doe@example.com"
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "1234567890"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers.CheckPhoneEmailExistRes": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email_exist": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"phone_number_exist": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers.RegisterCodeReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "john.doe@example.com"
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "1234567890"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers.RegisterUserReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "john.doe@example.com"
|
||||||
|
},
|
||||||
|
"first_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "John"
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Doe"
|
||||||
|
},
|
||||||
|
"otp": {
|
||||||
|
"description": "Role string",
|
||||||
|
"type": "string",
|
||||||
|
"example": "123456"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "password123"
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "1234567890"
|
||||||
|
},
|
||||||
|
"referal_code": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "ABC123"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers.ResetCodeReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "john.doe@example.com"
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "1234567890"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers.ResetPasswordReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"otp": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"phoneNumber": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers.UserProfileRes": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"created_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"email_verified": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"first_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"phone_verified": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"$ref": "#/definitions/domain.Role"
|
||||||
|
},
|
||||||
|
"suspended": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"suspended_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers.loginCustomerReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
"email": {
|
||||||
|
|
@ -199,7 +628,7 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"httpserver.loginCustomerRes": {
|
"handlers.loginCustomerRes": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"access_token": {
|
"access_token": {
|
||||||
|
|
@ -210,7 +639,7 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"httpserver.logoutReq": {
|
"handlers.logoutReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"refresh_token": {
|
"refresh_token": {
|
||||||
|
|
@ -218,7 +647,7 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"httpserver.refreshToken": {
|
"handlers.refreshToken": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"access_token": {
|
"access_token": {
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/httpserver.loginCustomerReq"
|
"$ref": "#/definitions/handlers.loginCustomerReq"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -44,7 +44,7 @@
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/httpserver.loginCustomerRes"
|
"$ref": "#/definitions/handlers.loginCustomerRes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -88,7 +88,7 @@
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/httpserver.logoutReq"
|
"$ref": "#/definitions/handlers.logoutReq"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -140,7 +140,7 @@
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"required": true,
|
"required": true,
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/httpserver.refreshToken"
|
"$ref": "#/definitions/handlers.refreshToken"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -148,7 +148,7 @@
|
||||||
"200": {
|
"200": {
|
||||||
"description": "OK",
|
"description": "OK",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/httpserver.loginCustomerRes"
|
"$ref": "#/definitions/handlers.loginCustomerRes"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
|
|
@ -171,10 +171,439 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/user/checkPhoneEmailExist": {
|
||||||
|
"post": {
|
||||||
|
"description": "Check if phone number or email exist",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Check if phone number or email exist",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Check phone number or email exist",
|
||||||
|
"name": "checkPhoneEmailExist",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.CheckPhoneEmailExistReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.CheckPhoneEmailExistRes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/user/profile": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"Bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Get user profile",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Get user profile",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.UserProfileRes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/user/register": {
|
||||||
|
"post": {
|
||||||
|
"description": "Register user",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Register user",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Register user",
|
||||||
|
"name": "registerUser",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.RegisterUserReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/user/resetPassword": {
|
||||||
|
"post": {
|
||||||
|
"description": "Reset password",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Reset password",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Reset password",
|
||||||
|
"name": "resetPassword",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.ResetPasswordReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/user/sendRegisterCode": {
|
||||||
|
"post": {
|
||||||
|
"description": "Send register code",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Send register code",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Send register code",
|
||||||
|
"name": "registerCode",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.RegisterCodeReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/user/sendResetCode": {
|
||||||
|
"post": {
|
||||||
|
"description": "Send reset code",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Send reset code",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Send reset code",
|
||||||
|
"name": "resetCode",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/handlers.ResetCodeReq"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.APIResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"httpserver.loginCustomerReq": {
|
"domain.Role": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": [
|
||||||
|
"admin",
|
||||||
|
"customer",
|
||||||
|
"super_admin",
|
||||||
|
"branch_manager",
|
||||||
|
"cashier"
|
||||||
|
],
|
||||||
|
"x-enum-varnames": [
|
||||||
|
"RoleAdmin",
|
||||||
|
"RoleCustomer",
|
||||||
|
"RoleSuperAdmin",
|
||||||
|
"RoleBranchManager",
|
||||||
|
"RoleCashier"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"handlers.CheckPhoneEmailExistReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "john.doe@example.com"
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "1234567890"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers.CheckPhoneEmailExistRes": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email_exist": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"phone_number_exist": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers.RegisterCodeReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "john.doe@example.com"
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "1234567890"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers.RegisterUserReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "john.doe@example.com"
|
||||||
|
},
|
||||||
|
"first_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "John"
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "Doe"
|
||||||
|
},
|
||||||
|
"otp": {
|
||||||
|
"description": "Role string",
|
||||||
|
"type": "string",
|
||||||
|
"example": "123456"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "password123"
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "1234567890"
|
||||||
|
},
|
||||||
|
"referal_code": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "ABC123"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers.ResetCodeReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "john.doe@example.com"
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "1234567890"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers.ResetPasswordReq": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"otp": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"phoneNumber": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers.UserProfileRes": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"created_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"email_verified": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"first_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"phone_number": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"phone_verified": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"$ref": "#/definitions/domain.Role"
|
||||||
|
},
|
||||||
|
"suspended": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"suspended_at": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers.loginCustomerReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"email": {
|
"email": {
|
||||||
|
|
@ -191,7 +620,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"httpserver.loginCustomerRes": {
|
"handlers.loginCustomerRes": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"access_token": {
|
"access_token": {
|
||||||
|
|
@ -202,7 +631,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"httpserver.logoutReq": {
|
"handlers.logoutReq": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"refresh_token": {
|
"refresh_token": {
|
||||||
|
|
@ -210,7 +639,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"httpserver.refreshToken": {
|
"handlers.refreshToken": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"access_token": {
|
"access_token": {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,116 @@
|
||||||
definitions:
|
definitions:
|
||||||
httpserver.loginCustomerReq:
|
domain.Role:
|
||||||
|
enum:
|
||||||
|
- admin
|
||||||
|
- customer
|
||||||
|
- super_admin
|
||||||
|
- branch_manager
|
||||||
|
- cashier
|
||||||
|
type: string
|
||||||
|
x-enum-varnames:
|
||||||
|
- RoleAdmin
|
||||||
|
- RoleCustomer
|
||||||
|
- RoleSuperAdmin
|
||||||
|
- RoleBranchManager
|
||||||
|
- RoleCashier
|
||||||
|
handlers.CheckPhoneEmailExistReq:
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
example: john.doe@example.com
|
||||||
|
type: string
|
||||||
|
phone_number:
|
||||||
|
example: "1234567890"
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handlers.CheckPhoneEmailExistRes:
|
||||||
|
properties:
|
||||||
|
email_exist:
|
||||||
|
type: boolean
|
||||||
|
phone_number_exist:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
|
handlers.RegisterCodeReq:
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
example: john.doe@example.com
|
||||||
|
type: string
|
||||||
|
phone_number:
|
||||||
|
example: "1234567890"
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handlers.RegisterUserReq:
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
example: john.doe@example.com
|
||||||
|
type: string
|
||||||
|
first_name:
|
||||||
|
example: John
|
||||||
|
type: string
|
||||||
|
last_name:
|
||||||
|
example: Doe
|
||||||
|
type: string
|
||||||
|
otp:
|
||||||
|
description: Role string
|
||||||
|
example: "123456"
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
example: password123
|
||||||
|
type: string
|
||||||
|
phone_number:
|
||||||
|
example: "1234567890"
|
||||||
|
type: string
|
||||||
|
referal_code:
|
||||||
|
example: ABC123
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handlers.ResetCodeReq:
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
example: john.doe@example.com
|
||||||
|
type: string
|
||||||
|
phone_number:
|
||||||
|
example: "1234567890"
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handlers.ResetPasswordReq:
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
otp:
|
||||||
|
type: string
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
phoneNumber:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handlers.UserProfileRes:
|
||||||
|
properties:
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
email_verified:
|
||||||
|
type: boolean
|
||||||
|
first_name:
|
||||||
|
type: string
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
last_name:
|
||||||
|
type: string
|
||||||
|
phone_number:
|
||||||
|
type: string
|
||||||
|
phone_verified:
|
||||||
|
type: boolean
|
||||||
|
role:
|
||||||
|
$ref: '#/definitions/domain.Role'
|
||||||
|
suspended:
|
||||||
|
type: boolean
|
||||||
|
suspended_at:
|
||||||
|
type: string
|
||||||
|
updated_at:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
handlers.loginCustomerReq:
|
||||||
properties:
|
properties:
|
||||||
email:
|
email:
|
||||||
example: john.doe@example.com
|
example: john.doe@example.com
|
||||||
|
|
@ -11,19 +122,19 @@ definitions:
|
||||||
example: "1234567890"
|
example: "1234567890"
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
httpserver.loginCustomerRes:
|
handlers.loginCustomerRes:
|
||||||
properties:
|
properties:
|
||||||
access_token:
|
access_token:
|
||||||
type: string
|
type: string
|
||||||
refresh_token:
|
refresh_token:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
httpserver.logoutReq:
|
handlers.logoutReq:
|
||||||
properties:
|
properties:
|
||||||
refresh_token:
|
refresh_token:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
type: object
|
||||||
httpserver.refreshToken:
|
handlers.refreshToken:
|
||||||
properties:
|
properties:
|
||||||
access_token:
|
access_token:
|
||||||
type: string
|
type: string
|
||||||
|
|
@ -73,14 +184,14 @@ paths:
|
||||||
name: login
|
name: login
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/httpserver.loginCustomerReq'
|
$ref: '#/definitions/handlers.loginCustomerReq'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/httpserver.loginCustomerRes'
|
$ref: '#/definitions/handlers.loginCustomerRes'
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
|
|
@ -107,7 +218,7 @@ paths:
|
||||||
name: logout
|
name: logout
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/httpserver.logoutReq'
|
$ref: '#/definitions/handlers.logoutReq'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
|
|
@ -141,14 +252,14 @@ paths:
|
||||||
name: refresh
|
name: refresh
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/httpserver.refreshToken'
|
$ref: '#/definitions/handlers.refreshToken'
|
||||||
produces:
|
produces:
|
||||||
- application/json
|
- application/json
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: OK
|
description: OK
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/httpserver.loginCustomerRes'
|
$ref: '#/definitions/handlers.loginCustomerRes'
|
||||||
"400":
|
"400":
|
||||||
description: Bad Request
|
description: Bad Request
|
||||||
schema:
|
schema:
|
||||||
|
|
@ -164,6 +275,181 @@ paths:
|
||||||
summary: Refresh token
|
summary: Refresh token
|
||||||
tags:
|
tags:
|
||||||
- auth
|
- auth
|
||||||
|
/user/checkPhoneEmailExist:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Check if phone number or email exist
|
||||||
|
parameters:
|
||||||
|
- description: Check phone number or email exist
|
||||||
|
in: body
|
||||||
|
name: checkPhoneEmailExist
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handlers.CheckPhoneEmailExistReq'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handlers.CheckPhoneEmailExistRes'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
summary: Check if phone number or email exist
|
||||||
|
tags:
|
||||||
|
- user
|
||||||
|
/user/profile:
|
||||||
|
get:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Get user profile
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handlers.UserProfileRes'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
security:
|
||||||
|
- Bearer: []
|
||||||
|
summary: Get user profile
|
||||||
|
tags:
|
||||||
|
- user
|
||||||
|
/user/register:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Register user
|
||||||
|
parameters:
|
||||||
|
- description: Register user
|
||||||
|
in: body
|
||||||
|
name: registerUser
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handlers.RegisterUserReq'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
summary: Register user
|
||||||
|
tags:
|
||||||
|
- user
|
||||||
|
/user/resetPassword:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Reset password
|
||||||
|
parameters:
|
||||||
|
- description: Reset password
|
||||||
|
in: body
|
||||||
|
name: resetPassword
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handlers.ResetPasswordReq'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
summary: Reset password
|
||||||
|
tags:
|
||||||
|
- user
|
||||||
|
/user/sendRegisterCode:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Send register code
|
||||||
|
parameters:
|
||||||
|
- description: Send register code
|
||||||
|
in: body
|
||||||
|
name: registerCode
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handlers.RegisterCodeReq'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
summary: Send register code
|
||||||
|
tags:
|
||||||
|
- user
|
||||||
|
/user/sendResetCode:
|
||||||
|
post:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Send reset code
|
||||||
|
parameters:
|
||||||
|
- description: Send reset code
|
||||||
|
in: body
|
||||||
|
name: resetCode
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/handlers.ResetCodeReq'
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.APIResponse'
|
||||||
|
summary: Send reset code
|
||||||
|
tags:
|
||||||
|
- user
|
||||||
securityDefinitions:
|
securityDefinitions:
|
||||||
Bearer:
|
Bearer:
|
||||||
in: header
|
in: header
|
||||||
|
|
|
||||||
|
|
@ -55,13 +55,13 @@ func (q *Queries) GetRefreshToken(ctx context.Context, token string) (RefreshTok
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetUserByEmailPhone = `-- name: GetUserByEmailPhone :one
|
const GetUserByEmailPhone = `-- name: GetUserByEmailPhone :one
|
||||||
SELECT id, first_name, last_name, email, phone_number, password, role, verified, created_at, updated_at FROM users
|
SELECT id, first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at, suspended_at, suspended FROM users
|
||||||
WHERE email = $1 OR phone_number = $2
|
WHERE email = $1 OR phone_number = $2
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetUserByEmailPhoneParams struct {
|
type GetUserByEmailPhoneParams struct {
|
||||||
Email string
|
Email pgtype.Text
|
||||||
PhoneNumber string
|
PhoneNumber pgtype.Text
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPhoneParams) (User, error) {
|
func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPhoneParams) (User, error) {
|
||||||
|
|
@ -73,11 +73,14 @@ func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPho
|
||||||
&i.LastName,
|
&i.LastName,
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.PhoneNumber,
|
&i.PhoneNumber,
|
||||||
&i.Password,
|
|
||||||
&i.Role,
|
&i.Role,
|
||||||
&i.Verified,
|
&i.Password,
|
||||||
|
&i.EmailVerified,
|
||||||
|
&i.PhoneVerified,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
|
&i.SuspendedAt,
|
||||||
|
&i.Suspended,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,18 @@ import (
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Otp struct {
|
||||||
|
ID int64
|
||||||
|
SentTo string
|
||||||
|
Medium string
|
||||||
|
OtpFor string
|
||||||
|
Otp string
|
||||||
|
Used bool
|
||||||
|
UsedAt pgtype.Timestamptz
|
||||||
|
CreatedAt pgtype.Timestamptz
|
||||||
|
ExpiresAt pgtype.Timestamptz
|
||||||
|
}
|
||||||
|
|
||||||
type RefreshToken struct {
|
type RefreshToken struct {
|
||||||
ID int64
|
ID int64
|
||||||
UserID int64
|
UserID int64
|
||||||
|
|
@ -21,11 +33,14 @@ type User struct {
|
||||||
ID int64
|
ID int64
|
||||||
FirstName string
|
FirstName string
|
||||||
LastName string
|
LastName string
|
||||||
Email string
|
Email pgtype.Text
|
||||||
PhoneNumber string
|
PhoneNumber pgtype.Text
|
||||||
Password []byte
|
|
||||||
Role string
|
Role string
|
||||||
Verified pgtype.Bool
|
Password []byte
|
||||||
|
EmailVerified bool
|
||||||
|
PhoneVerified bool
|
||||||
CreatedAt pgtype.Timestamptz
|
CreatedAt pgtype.Timestamptz
|
||||||
UpdatedAt pgtype.Timestamptz
|
UpdatedAt pgtype.Timestamptz
|
||||||
|
SuspendedAt pgtype.Timestamptz
|
||||||
|
Suspended bool
|
||||||
}
|
}
|
||||||
|
|
|
||||||
77
gen/db/otp.sql.go
Normal file
77
gen/db/otp.sql.go
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.26.0
|
||||||
|
// source: otp.sql
|
||||||
|
|
||||||
|
package dbgen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CreateOtp = `-- name: CreateOtp :exec
|
||||||
|
INSERT INTO otps (sent_to, medium, otp_for, otp, used, created_at, expires_at)
|
||||||
|
VALUES ($1, $2, $3, $4, FALSE, CURRENT_TIMESTAMP, $5)
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateOtpParams struct {
|
||||||
|
SentTo string
|
||||||
|
Medium string
|
||||||
|
OtpFor string
|
||||||
|
Otp string
|
||||||
|
ExpiresAt pgtype.Timestamptz
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateOtp(ctx context.Context, arg CreateOtpParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, CreateOtp,
|
||||||
|
arg.SentTo,
|
||||||
|
arg.Medium,
|
||||||
|
arg.OtpFor,
|
||||||
|
arg.Otp,
|
||||||
|
arg.ExpiresAt,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetOtp = `-- name: GetOtp :one
|
||||||
|
SELECT id, sent_to, medium, otp_for, otp, used, used_at, created_at, expires_at
|
||||||
|
FROM otps
|
||||||
|
WHERE sent_to = $1 AND otp_for = $2 AND medium = $3
|
||||||
|
ORDER BY created_at DESC LIMIT 1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetOtpParams struct {
|
||||||
|
SentTo string
|
||||||
|
OtpFor string
|
||||||
|
Medium string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetOtp(ctx context.Context, arg GetOtpParams) (Otp, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetOtp, arg.SentTo, arg.OtpFor, arg.Medium)
|
||||||
|
var i Otp
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SentTo,
|
||||||
|
&i.Medium,
|
||||||
|
&i.OtpFor,
|
||||||
|
&i.Otp,
|
||||||
|
&i.Used,
|
||||||
|
&i.UsedAt,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.ExpiresAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const MarkOtpAsUsed = `-- name: MarkOtpAsUsed :exec
|
||||||
|
UPDATE otps
|
||||||
|
SET used = TRUE, used_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) MarkOtpAsUsed(ctx context.Context, id int64) error {
|
||||||
|
_, err := q.db.Exec(ctx, MarkOtpAsUsed, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
@ -11,42 +11,81 @@ import (
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const CheckPhoneEmailExist = `-- name: CheckPhoneEmailExist :one
|
||||||
|
SELECT
|
||||||
|
EXISTS (SELECT 1 FROM users WHERE users.phone_number = $1 AND users.phone_number IS NOT NULL) AS phone_exists,
|
||||||
|
EXISTS (SELECT 1 FROM users WHERE users.email = $2 AND users.email IS NOT NULL) AS email_exists
|
||||||
|
`
|
||||||
|
|
||||||
|
type CheckPhoneEmailExistParams struct {
|
||||||
|
PhoneNumber pgtype.Text
|
||||||
|
Email pgtype.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckPhoneEmailExistRow struct {
|
||||||
|
PhoneExists bool
|
||||||
|
EmailExists bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CheckPhoneEmailExist(ctx context.Context, arg CheckPhoneEmailExistParams) (CheckPhoneEmailExistRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, CheckPhoneEmailExist, arg.PhoneNumber, arg.Email)
|
||||||
|
var i CheckPhoneEmailExistRow
|
||||||
|
err := row.Scan(&i.PhoneExists, &i.EmailExists)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
const CreateUser = `-- name: CreateUser :one
|
const CreateUser = `-- name: CreateUser :one
|
||||||
INSERT INTO users (first_name, last_name, email, phone_number, password, role, verified)
|
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
INSERT INTO users (first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at)
|
||||||
RETURNING id, first_name, last_name, email, phone_number, password, role, verified, created_at, updated_at
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||||
|
RETURNING id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at
|
||||||
`
|
`
|
||||||
|
|
||||||
type CreateUserParams struct {
|
type CreateUserParams struct {
|
||||||
FirstName string
|
FirstName string
|
||||||
LastName string
|
LastName string
|
||||||
Email string
|
Email pgtype.Text
|
||||||
PhoneNumber string
|
PhoneNumber pgtype.Text
|
||||||
Password []byte
|
|
||||||
Role string
|
Role string
|
||||||
Verified pgtype.Bool
|
Password []byte
|
||||||
|
EmailVerified bool
|
||||||
|
PhoneVerified bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) {
|
type CreateUserRow struct {
|
||||||
|
ID int64
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
Email pgtype.Text
|
||||||
|
PhoneNumber pgtype.Text
|
||||||
|
Role string
|
||||||
|
EmailVerified bool
|
||||||
|
PhoneVerified bool
|
||||||
|
CreatedAt pgtype.Timestamptz
|
||||||
|
UpdatedAt pgtype.Timestamptz
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (CreateUserRow, error) {
|
||||||
row := q.db.QueryRow(ctx, CreateUser,
|
row := q.db.QueryRow(ctx, CreateUser,
|
||||||
arg.FirstName,
|
arg.FirstName,
|
||||||
arg.LastName,
|
arg.LastName,
|
||||||
arg.Email,
|
arg.Email,
|
||||||
arg.PhoneNumber,
|
arg.PhoneNumber,
|
||||||
arg.Password,
|
|
||||||
arg.Role,
|
arg.Role,
|
||||||
arg.Verified,
|
arg.Password,
|
||||||
|
arg.EmailVerified,
|
||||||
|
arg.PhoneVerified,
|
||||||
)
|
)
|
||||||
var i User
|
var i CreateUserRow
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.FirstName,
|
&i.FirstName,
|
||||||
&i.LastName,
|
&i.LastName,
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.PhoneNumber,
|
&i.PhoneNumber,
|
||||||
&i.Password,
|
|
||||||
&i.Role,
|
&i.Role,
|
||||||
&i.Verified,
|
&i.EmailVerified,
|
||||||
|
&i.PhoneVerified,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
)
|
)
|
||||||
|
|
@ -54,7 +93,8 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, e
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeleteUser = `-- name: DeleteUser :exec
|
const DeleteUser = `-- name: DeleteUser :exec
|
||||||
DELETE FROM users WHERE id = $1
|
DELETE FROM users
|
||||||
|
WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) DeleteUser(ctx context.Context, id int64) error {
|
func (q *Queries) DeleteUser(ctx context.Context, id int64) error {
|
||||||
|
|
@ -63,27 +103,41 @@ func (q *Queries) DeleteUser(ctx context.Context, id int64) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
const GetAllUsers = `-- name: GetAllUsers :many
|
const GetAllUsers = `-- name: GetAllUsers :many
|
||||||
SELECT id, first_name, last_name, email, phone_number, password, role, verified, created_at, updated_at FROM users
|
SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at
|
||||||
|
FROM users
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetAllUsers(ctx context.Context) ([]User, error) {
|
type GetAllUsersRow struct {
|
||||||
|
ID int64
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
Email pgtype.Text
|
||||||
|
PhoneNumber pgtype.Text
|
||||||
|
Role string
|
||||||
|
EmailVerified bool
|
||||||
|
PhoneVerified bool
|
||||||
|
CreatedAt pgtype.Timestamptz
|
||||||
|
UpdatedAt pgtype.Timestamptz
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetAllUsers(ctx context.Context) ([]GetAllUsersRow, error) {
|
||||||
rows, err := q.db.Query(ctx, GetAllUsers)
|
rows, err := q.db.Query(ctx, GetAllUsers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
var items []User
|
var items []GetAllUsersRow
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var i User
|
var i GetAllUsersRow
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.FirstName,
|
&i.FirstName,
|
||||||
&i.LastName,
|
&i.LastName,
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.PhoneNumber,
|
&i.PhoneNumber,
|
||||||
&i.Password,
|
|
||||||
&i.Role,
|
&i.Role,
|
||||||
&i.Verified,
|
&i.EmailVerified,
|
||||||
|
&i.PhoneVerified,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
|
@ -97,8 +151,47 @@ func (q *Queries) GetAllUsers(ctx context.Context) ([]User, error) {
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const GetUserByEmail = `-- name: GetUserByEmail :one
|
||||||
|
SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at
|
||||||
|
FROM users
|
||||||
|
WHERE email = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetUserByEmailRow struct {
|
||||||
|
ID int64
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
Email pgtype.Text
|
||||||
|
PhoneNumber pgtype.Text
|
||||||
|
Role string
|
||||||
|
EmailVerified bool
|
||||||
|
PhoneVerified bool
|
||||||
|
CreatedAt pgtype.Timestamptz
|
||||||
|
UpdatedAt pgtype.Timestamptz
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetUserByEmail(ctx context.Context, email pgtype.Text) (GetUserByEmailRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetUserByEmail, email)
|
||||||
|
var i GetUserByEmailRow
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.FirstName,
|
||||||
|
&i.LastName,
|
||||||
|
&i.Email,
|
||||||
|
&i.PhoneNumber,
|
||||||
|
&i.Role,
|
||||||
|
&i.EmailVerified,
|
||||||
|
&i.PhoneVerified,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
const GetUserByID = `-- name: GetUserByID :one
|
const GetUserByID = `-- name: GetUserByID :one
|
||||||
SELECT id, first_name, last_name, email, phone_number, password, role, verified, created_at, updated_at FROM users WHERE id = $1
|
SELECT id, first_name, last_name, email, phone_number, role, password, email_verified, phone_verified, created_at, updated_at, suspended_at, suspended
|
||||||
|
FROM users
|
||||||
|
WHERE id = $1
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
|
func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
|
||||||
|
|
@ -110,40 +203,95 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (User, error) {
|
||||||
&i.LastName,
|
&i.LastName,
|
||||||
&i.Email,
|
&i.Email,
|
||||||
&i.PhoneNumber,
|
&i.PhoneNumber,
|
||||||
&i.Password,
|
|
||||||
&i.Role,
|
&i.Role,
|
||||||
&i.Verified,
|
&i.Password,
|
||||||
|
&i.EmailVerified,
|
||||||
|
&i.PhoneVerified,
|
||||||
|
&i.CreatedAt,
|
||||||
|
&i.UpdatedAt,
|
||||||
|
&i.SuspendedAt,
|
||||||
|
&i.Suspended,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const GetUserByPhone = `-- name: GetUserByPhone :one
|
||||||
|
SELECT id, first_name, last_name, email, phone_number, role, email_verified, phone_verified, created_at, updated_at
|
||||||
|
FROM users
|
||||||
|
WHERE phone_number = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetUserByPhoneRow struct {
|
||||||
|
ID int64
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
Email pgtype.Text
|
||||||
|
PhoneNumber pgtype.Text
|
||||||
|
Role string
|
||||||
|
EmailVerified bool
|
||||||
|
PhoneVerified bool
|
||||||
|
CreatedAt pgtype.Timestamptz
|
||||||
|
UpdatedAt pgtype.Timestamptz
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetUserByPhone(ctx context.Context, phoneNumber pgtype.Text) (GetUserByPhoneRow, error) {
|
||||||
|
row := q.db.QueryRow(ctx, GetUserByPhone, phoneNumber)
|
||||||
|
var i GetUserByPhoneRow
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.FirstName,
|
||||||
|
&i.LastName,
|
||||||
|
&i.Email,
|
||||||
|
&i.PhoneNumber,
|
||||||
|
&i.Role,
|
||||||
|
&i.EmailVerified,
|
||||||
|
&i.PhoneVerified,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
&i.UpdatedAt,
|
&i.UpdatedAt,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const UpdatePassword = `-- name: UpdatePassword :exec
|
||||||
|
UPDATE users
|
||||||
|
SET password = $1, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE (email = $2 OR phone_number = $3)
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdatePasswordParams struct {
|
||||||
|
Password []byte
|
||||||
|
Email pgtype.Text
|
||||||
|
PhoneNumber pgtype.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdatePassword(ctx context.Context, arg UpdatePasswordParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, UpdatePassword, arg.Password, arg.Email, arg.PhoneNumber)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
const UpdateUser = `-- name: UpdateUser :exec
|
const UpdateUser = `-- name: UpdateUser :exec
|
||||||
UPDATE users SET first_name = $2, last_name = $3, email = $4, phone_number = $5, password = $6, role = $7, verified = $8, updated_at = CURRENT_TIMESTAMP WHERE id = $1
|
UPDATE users
|
||||||
|
SET first_name = $1, last_name = $2, email = $3, phone_number = $4, role = $5, updated_at = CURRENT_TIMESTAMP
|
||||||
|
WHERE id = $6
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateUserParams struct {
|
type UpdateUserParams struct {
|
||||||
ID int64
|
|
||||||
FirstName string
|
FirstName string
|
||||||
LastName string
|
LastName string
|
||||||
Email string
|
Email pgtype.Text
|
||||||
PhoneNumber string
|
PhoneNumber pgtype.Text
|
||||||
Password []byte
|
|
||||||
Role string
|
Role string
|
||||||
Verified pgtype.Bool
|
ID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error {
|
func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) error {
|
||||||
_, err := q.db.Exec(ctx, UpdateUser,
|
_, err := q.db.Exec(ctx, UpdateUser,
|
||||||
arg.ID,
|
|
||||||
arg.FirstName,
|
arg.FirstName,
|
||||||
arg.LastName,
|
arg.LastName,
|
||||||
arg.Email,
|
arg.Email,
|
||||||
arg.PhoneNumber,
|
arg.PhoneNumber,
|
||||||
arg.Password,
|
|
||||||
arg.Role,
|
arg.Role,
|
||||||
arg.Verified,
|
arg.ID,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,16 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrOtpNotFound = errors.New("otp not found")
|
||||||
|
ErrOtpAlreadyUsed = errors.New("otp already used")
|
||||||
|
ErrInvalidOtp = errors.New("invalid otp")
|
||||||
|
ErrOtpExpired = errors.New("otp expired")
|
||||||
|
)
|
||||||
|
|
||||||
type OtpFor string
|
type OtpFor string
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,13 @@
|
||||||
package domain
|
package domain
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUserNotFound = errors.New("user not found")
|
||||||
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int64
|
ID int64
|
||||||
|
|
|
||||||
18
internal/mocks/mock_email/email.go
Normal file
18
internal/mocks/mock_email/email.go
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
package mockemail
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockEmail struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMockEmail() *MockEmail {
|
||||||
|
return &MockEmail{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockEmail) SendEmailOTP(ctx context.Context, email string, otp string) error {
|
||||||
|
fmt.Println("MockEmail: Sending OTP to", email, "with OTP:", otp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
19
internal/mocks/mock_sms/sms.go
Normal file
19
internal/mocks/mock_sms/sms.go
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
package mocksms
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockSMS struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMockSMS() *MockSMS {
|
||||||
|
return &MockSMS{}
|
||||||
|
}
|
||||||
|
func (m *MockSMS) SendSMSOTP(ctx context.Context, phoneNumber, otp string) error {
|
||||||
|
fmt.Println("MockSMS: Sending OTP to", phoneNumber, "with OTP:", otp)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (m *MockSms){}
|
||||||
|
|
@ -13,8 +13,14 @@ import (
|
||||||
|
|
||||||
func (s *Store) GetUserByEmailPhone(ctx context.Context, email, phone string) (domain.User, error) {
|
func (s *Store) GetUserByEmailPhone(ctx context.Context, email, phone string) (domain.User, error) {
|
||||||
user, err := s.queries.GetUserByEmailPhone(ctx, dbgen.GetUserByEmailPhoneParams{
|
user, err := s.queries.GetUserByEmailPhone(ctx, dbgen.GetUserByEmailPhoneParams{
|
||||||
Email: email,
|
Email: pgtype.Text{
|
||||||
PhoneNumber: phone,
|
String: email,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
PhoneNumber: pgtype.Text{
|
||||||
|
String: phone,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
|
@ -26,8 +32,8 @@ func (s *Store) GetUserByEmailPhone(ctx context.Context, email, phone string) (d
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
FirstName: user.FirstName,
|
FirstName: user.FirstName,
|
||||||
LastName: user.LastName,
|
LastName: user.LastName,
|
||||||
Email: user.Email,
|
Email: user.Email.String,
|
||||||
PhoneNumber: user.PhoneNumber,
|
PhoneNumber: user.PhoneNumber.String,
|
||||||
Password: user.Password,
|
Password: user.Password,
|
||||||
Role: domain.Role(user.Role),
|
Role: domain.Role(user.Role),
|
||||||
}, nil
|
}, nil
|
||||||
|
|
|
||||||
50
internal/repository/otp.go
Normal file
50
internal/repository/otp.go
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Store) CreateOtp(ctx context.Context, otp domain.Otp) error {
|
||||||
|
return s.queries.CreateOtp(ctx, dbgen.CreateOtpParams{
|
||||||
|
SentTo: otp.SentTo,
|
||||||
|
Medium: string(otp.Medium),
|
||||||
|
OtpFor: string(otp.For),
|
||||||
|
Otp: otp.Otp,
|
||||||
|
ExpiresAt: pgtype.Timestamptz{
|
||||||
|
Time: otp.ExpiresAt,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
func (s *Store) GetOtp(ctx context.Context, sentTo string, sentfor domain.OtpFor, medium domain.OtpMedium) (domain.Otp, error) {
|
||||||
|
row, err := s.queries.GetOtp(ctx, dbgen.GetOtpParams{
|
||||||
|
SentTo: sentTo,
|
||||||
|
Medium: string(medium),
|
||||||
|
OtpFor: string(sentfor),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
return domain.Otp{}, domain.ErrOtpNotFound
|
||||||
|
}
|
||||||
|
return domain.Otp{}, err
|
||||||
|
}
|
||||||
|
return domain.Otp{
|
||||||
|
ID: row.ID,
|
||||||
|
SentTo: row.SentTo,
|
||||||
|
Medium: domain.OtpMedium(row.Medium),
|
||||||
|
For: domain.OtpFor(row.OtpFor),
|
||||||
|
Otp: row.Otp,
|
||||||
|
Used: row.Used,
|
||||||
|
UsedAt: row.UsedAt.Time,
|
||||||
|
CreatedAt: row.CreatedAt.Time,
|
||||||
|
ExpiresAt: row.ExpiresAt.Time,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
func (s *Store) MarkOtpAsUsed(ctx context.Context, otp domain.Otp) error {
|
||||||
|
return s.queries.MarkOtpAsUsed(ctx, otp.ID)
|
||||||
|
}
|
||||||
|
|
@ -2,47 +2,70 @@ package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *Store) CreateUser(ctx context.Context, firstName, lastName, email, phoneNumber, password, role string, verified bool) (domain.User, error) {
|
func (s *Store) CreateUser(ctx context.Context, user domain.User, usedOtpId int64) (domain.User, error) {
|
||||||
user, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{
|
err := s.queries.MarkOtpAsUsed(ctx, usedOtpId)
|
||||||
FirstName: firstName,
|
if err != nil {
|
||||||
LastName: lastName,
|
return domain.User{}, err
|
||||||
Email: email,
|
}
|
||||||
PhoneNumber: phoneNumber,
|
userRes, err := s.queries.CreateUser(ctx, dbgen.CreateUserParams{
|
||||||
// Password: password,
|
FirstName: user.FirstName,
|
||||||
Role: role,
|
LastName: user.LastName,
|
||||||
|
Email: pgtype.Text{
|
||||||
|
String: user.Email,
|
||||||
|
Valid: user.Email != "",
|
||||||
|
},
|
||||||
|
PhoneNumber: pgtype.Text{
|
||||||
|
String: user.PhoneNumber,
|
||||||
|
Valid: user.PhoneNumber != "",
|
||||||
|
},
|
||||||
|
Password: user.Password,
|
||||||
|
Role: string(user.Role),
|
||||||
|
EmailVerified: user.EmailVerified,
|
||||||
|
PhoneVerified: user.PhoneVerified,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return domain.User{}, err
|
return domain.User{}, err
|
||||||
}
|
}
|
||||||
return domain.User{
|
return domain.User{
|
||||||
ID: user.ID,
|
ID: userRes.ID,
|
||||||
FirstName: user.FirstName,
|
FirstName: userRes.FirstName,
|
||||||
LastName: user.LastName,
|
LastName: userRes.LastName,
|
||||||
Email: user.Email,
|
Email: userRes.Email.String,
|
||||||
PhoneNumber: user.PhoneNumber,
|
PhoneNumber: userRes.PhoneNumber.String,
|
||||||
Password: user.Password,
|
Role: domain.Role(userRes.Role),
|
||||||
// Role: user.Role,
|
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
func (s *Store) GetUserByID(ctx context.Context, id int64) (domain.User, error) {
|
func (s *Store) GetUserByID(ctx context.Context, id int64) (domain.User, error) {
|
||||||
user, err := s.queries.GetUserByID(ctx, id)
|
user, err := s.queries.GetUserByID(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return domain.User{}, domain.ErrUserNotFound
|
||||||
|
}
|
||||||
return domain.User{}, err
|
return domain.User{}, err
|
||||||
}
|
}
|
||||||
return domain.User{
|
return domain.User{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
FirstName: user.FirstName,
|
FirstName: user.FirstName,
|
||||||
LastName: user.LastName,
|
LastName: user.LastName,
|
||||||
Email: user.Email,
|
Email: user.Email.String,
|
||||||
PhoneNumber: user.PhoneNumber,
|
PhoneNumber: user.PhoneNumber.String,
|
||||||
|
Role: domain.Role(user.Role),
|
||||||
|
EmailVerified: user.EmailVerified,
|
||||||
Password: user.Password,
|
Password: user.Password,
|
||||||
// Role: user.Role,
|
PhoneVerified: user.PhoneVerified,
|
||||||
|
CreatedAt: user.CreatedAt.Time,
|
||||||
|
UpdatedAt: user.UpdatedAt.Time,
|
||||||
|
SuspendedAt: user.SuspendedAt.Time,
|
||||||
|
Suspended: user.Suspended,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
func (s *Store) GetAllUsers(ctx context.Context) ([]domain.User, error) {
|
func (s *Store) GetAllUsers(ctx context.Context) ([]domain.User, error) {
|
||||||
|
|
@ -50,32 +73,118 @@ func (s *Store) GetAllUsers(ctx context.Context) ([]domain.User, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var result []domain.User
|
userList := make([]domain.User, len(users))
|
||||||
for _, user := range users {
|
for i, user := range users {
|
||||||
result = append(result, domain.User{
|
userList[i] = domain.User{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
FirstName: user.FirstName,
|
FirstName: user.FirstName,
|
||||||
LastName: user.LastName,
|
LastName: user.LastName,
|
||||||
Email: user.Email,
|
Email: user.Email.String,
|
||||||
PhoneNumber: user.PhoneNumber,
|
PhoneNumber: user.PhoneNumber.String,
|
||||||
Password: user.Password,
|
Role: domain.Role(user.Role),
|
||||||
// Role: user.Role,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return result, nil
|
}
|
||||||
|
return userList, nil
|
||||||
}
|
}
|
||||||
func (s *Store) UpdateUser(ctx context.Context, id int64, firstName, lastName, email, phoneNumber, password, role string, verified bool) error {
|
func (s *Store) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error {
|
||||||
err := s.queries.UpdateUser(ctx, dbgen.UpdateUserParams{
|
err := s.queries.UpdateUser(ctx, dbgen.UpdateUserParams{
|
||||||
ID: id,
|
// ID: user.ID,
|
||||||
FirstName: firstName,
|
// FirstName: user.FirstName,
|
||||||
LastName: lastName,
|
// LastName: user.LastName,
|
||||||
Email: email,
|
// Email: user.Email,
|
||||||
PhoneNumber: phoneNumber,
|
// PhoneNumber: user.PhoneNumber,
|
||||||
// Password: password,
|
|
||||||
Role: role,
|
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
func (s *Store) DeleteUser(ctx context.Context, id int64) error {
|
func (s *Store) DeleteUser(ctx context.Context, id int64) error {
|
||||||
return s.queries.DeleteUser(ctx, id)
|
err := s.queries.DeleteUser(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (s *Store) CheckPhoneEmailExist(ctx context.Context, phoneNum, email string) (bool, bool, error) {
|
||||||
|
fmt.Printf("phoneNum: %s, email: %s\n", phoneNum, email)
|
||||||
|
row, err := s.queries.CheckPhoneEmailExist(ctx, dbgen.CheckPhoneEmailExistParams{
|
||||||
|
PhoneNumber: pgtype.Text{
|
||||||
|
String: phoneNum,
|
||||||
|
Valid: phoneNum != "",
|
||||||
|
},
|
||||||
|
Email: pgtype.Text{
|
||||||
|
String: email,
|
||||||
|
|
||||||
|
Valid: email != "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
fmt.Printf("row: %+v\n", row)
|
||||||
|
if err != nil {
|
||||||
|
return false, false, err
|
||||||
|
}
|
||||||
|
return row.EmailExists, row.PhoneExists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) GetUserByEmail(ctx context.Context, email string) (domain.User, error) {
|
||||||
|
user, err := s.queries.GetUserByEmail(ctx, pgtype.Text{
|
||||||
|
String: email,
|
||||||
|
Valid: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return domain.User{}, domain.ErrUserNotFound
|
||||||
|
}
|
||||||
|
return domain.User{}, err
|
||||||
|
}
|
||||||
|
return domain.User{
|
||||||
|
ID: user.ID,
|
||||||
|
FirstName: user.FirstName,
|
||||||
|
LastName: user.LastName,
|
||||||
|
Email: user.Email.String,
|
||||||
|
PhoneNumber: user.PhoneNumber.String,
|
||||||
|
Role: domain.Role(user.Role),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
func (s *Store) GetUserByPhone(ctx context.Context, phoneNum string) (domain.User, error) {
|
||||||
|
user, err := s.queries.GetUserByPhone(ctx, pgtype.Text{
|
||||||
|
String: phoneNum,
|
||||||
|
Valid: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return domain.User{}, domain.ErrUserNotFound
|
||||||
|
}
|
||||||
|
return domain.User{}, err
|
||||||
|
}
|
||||||
|
return domain.User{
|
||||||
|
ID: user.ID,
|
||||||
|
FirstName: user.FirstName,
|
||||||
|
LastName: user.LastName,
|
||||||
|
Email: user.Email.String,
|
||||||
|
PhoneNumber: user.PhoneNumber.String,
|
||||||
|
Role: domain.Role(user.Role),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) UpdatePassword(ctx context.Context, identifier string, password []byte, usedOtpId int64) error {
|
||||||
|
err := s.queries.MarkOtpAsUsed(ctx, usedOtpId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = s.queries.UpdatePassword(ctx, dbgen.UpdatePasswordParams{
|
||||||
|
Password: password,
|
||||||
|
Email: pgtype.Text{
|
||||||
|
String: identifier,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
PhoneNumber: pgtype.Text{
|
||||||
|
String: identifier,
|
||||||
|
Valid: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
44
internal/services/user/common.go
Normal file
44
internal/services/user/common.go
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium) error {
|
||||||
|
otpCode := "123456" // Generate OTP code
|
||||||
|
|
||||||
|
otp := domain.Otp{
|
||||||
|
SentTo: sentTo,
|
||||||
|
Medium: medium,
|
||||||
|
For: otpFor,
|
||||||
|
Otp: otpCode,
|
||||||
|
Used: false,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
ExpiresAt: time.Now().Add(OtpExpiry),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.otpStore.CreateOtp(ctx, otp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch medium {
|
||||||
|
case domain.OtpMediumSms:
|
||||||
|
return s.smsGateway.SendSMSOTP(ctx, sentTo, otpCode)
|
||||||
|
case domain.OtpMediumEmail:
|
||||||
|
return s.emailGateway.SendEmailOTP(ctx, sentTo, otpCode)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func hashPassword(plaintextPassword string) ([]byte, error) {
|
||||||
|
hash, err := bcrypt.GenerateFromPassword([]byte(plaintextPassword), 12)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash, nil
|
||||||
|
}
|
||||||
|
|
@ -27,5 +27,4 @@ type EmailGateway interface {
|
||||||
type OtpStore interface {
|
type OtpStore interface {
|
||||||
CreateOtp(ctx context.Context, otp domain.Otp) error
|
CreateOtp(ctx context.Context, otp domain.Otp) error
|
||||||
GetOtp(ctx context.Context, sentTo string, sentfor domain.OtpFor, medium domain.OtpMedium) (domain.Otp, error)
|
GetOtp(ctx context.Context, sentTo string, sentfor domain.OtpFor, medium domain.OtpMedium) (domain.Otp, error)
|
||||||
MarkUsed(ctx context.Context, id int64) error
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
79
internal/services/user/register.go
Normal file
79
internal/services/user/register.go
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Service) CheckPhoneEmailExist(ctx context.Context, phoneNum, email string) (bool, bool, error) { // email,phone,error
|
||||||
|
return s.userStore.CheckPhoneEmailExist(ctx, phoneNum, email)
|
||||||
|
}
|
||||||
|
func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, sentTo string) error {
|
||||||
|
var err error
|
||||||
|
// check if user exists
|
||||||
|
switch medium {
|
||||||
|
case domain.OtpMediumEmail:
|
||||||
|
_, err = s.userStore.GetUserByEmail(ctx, sentTo)
|
||||||
|
case domain.OtpMediumSms:
|
||||||
|
_, err = s.userStore.GetUserByPhone(ctx, sentTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil && err != domain.ErrUserNotFound {
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// send otp based on the medium
|
||||||
|
return s.SendOtp(ctx, sentTo, domain.OtpRegister, medium)
|
||||||
|
}
|
||||||
|
func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) { // normal
|
||||||
|
// get otp
|
||||||
|
fmt.Printf("registerReq: %+v\n", registerReq)
|
||||||
|
var sentTo string
|
||||||
|
if registerReq.OtpMedium == domain.OtpMediumEmail {
|
||||||
|
sentTo = registerReq.Email
|
||||||
|
} else {
|
||||||
|
sentTo = registerReq.PhoneNumber
|
||||||
|
}
|
||||||
|
//
|
||||||
|
otp, err := s.otpStore.GetOtp(
|
||||||
|
ctx, sentTo,
|
||||||
|
domain.OtpRegister, registerReq.OtpMedium)
|
||||||
|
if err != nil {
|
||||||
|
return domain.User{}, err
|
||||||
|
}
|
||||||
|
// verify otp
|
||||||
|
if otp.Used {
|
||||||
|
return domain.User{}, domain.ErrOtpAlreadyUsed
|
||||||
|
}
|
||||||
|
if time.Now().After(otp.ExpiresAt) {
|
||||||
|
return domain.User{}, domain.ErrOtpExpired
|
||||||
|
}
|
||||||
|
if otp.Otp != registerReq.Otp {
|
||||||
|
return domain.User{}, domain.ErrInvalidOtp
|
||||||
|
}
|
||||||
|
|
||||||
|
hashedPassword, err := hashPassword(registerReq.Password)
|
||||||
|
if err != nil {
|
||||||
|
return domain.User{}, err
|
||||||
|
}
|
||||||
|
userR := domain.User{
|
||||||
|
FirstName: registerReq.FirstName,
|
||||||
|
LastName: registerReq.LastName,
|
||||||
|
Email: registerReq.Email,
|
||||||
|
PhoneNumber: registerReq.PhoneNumber,
|
||||||
|
Password: hashedPassword,
|
||||||
|
Role: "user",
|
||||||
|
EmailVerified: registerReq.OtpMedium == domain.OtpMediumEmail,
|
||||||
|
PhoneVerified: registerReq.OtpMedium == domain.OtpMediumSms,
|
||||||
|
}
|
||||||
|
// create the user and mark otp as used
|
||||||
|
user, err := s.userStore.CreateUser(ctx, userR, otp.ID)
|
||||||
|
if err != nil {
|
||||||
|
return domain.User{}, err
|
||||||
|
}
|
||||||
|
return user, nil
|
||||||
|
}
|
||||||
63
internal/services/user/reset.go
Normal file
63
internal/services/user/reset.go
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, sentTo string) error {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
// check if user exists
|
||||||
|
switch medium {
|
||||||
|
case domain.OtpMediumEmail:
|
||||||
|
_, err = s.userStore.GetUserByEmail(ctx, sentTo)
|
||||||
|
case domain.OtpMediumSms:
|
||||||
|
_, err = s.userStore.GetUserByPhone(ctx, sentTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.SendOtp(ctx, sentTo, domain.OtpReset, medium)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ResetPassword(ctx context.Context, resetReq domain.ResetPasswordReq) error {
|
||||||
|
var sentTo string
|
||||||
|
if resetReq.OtpMedium == domain.OtpMediumEmail {
|
||||||
|
sentTo = resetReq.Email
|
||||||
|
} else {
|
||||||
|
sentTo = resetReq.PhoneNumber
|
||||||
|
}
|
||||||
|
otp, err := s.otpStore.GetOtp(
|
||||||
|
ctx, sentTo,
|
||||||
|
domain.OtpReset, resetReq.OtpMedium)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
//
|
||||||
|
if otp.Used {
|
||||||
|
return domain.ErrOtpAlreadyUsed
|
||||||
|
}
|
||||||
|
if time.Now().After(otp.ExpiresAt) {
|
||||||
|
return domain.ErrOtpExpired
|
||||||
|
}
|
||||||
|
if otp.Otp != resetReq.Otp {
|
||||||
|
return domain.ErrInvalidOtp
|
||||||
|
}
|
||||||
|
// hash password
|
||||||
|
hashedPassword, err := hashPassword(resetReq.Password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// reset pass and mark otp as used
|
||||||
|
err = s.userStore.UpdatePassword(ctx, sentTo, hashedPassword, otp.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -1,24 +1,13 @@
|
||||||
package user
|
package user
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
OtpExpiry = 5 * time.Minute
|
OtpExpiry = 5 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
ErrOtpAlreadyUsed = errors.New("otp already used")
|
|
||||||
ErrInvalidOtp = errors.New("invalid otp")
|
|
||||||
ErrOtpExpired = errors.New("otp expired")
|
|
||||||
)
|
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
userStore UserStore
|
userStore UserStore
|
||||||
otpStore OtpStore
|
otpStore OtpStore
|
||||||
|
|
@ -27,179 +16,14 @@ type Service struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewService(
|
func NewService(
|
||||||
userStore UserStore, RefreshExpiry int,
|
userStore UserStore,
|
||||||
otpStore OtpStore, smsGateway SmsGateway,
|
otpStore OtpStore, smsGateway SmsGateway,
|
||||||
emailGateway EmailGateway,
|
emailGateway EmailGateway,
|
||||||
) *Service {
|
) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
userStore: userStore,
|
userStore: userStore,
|
||||||
otpStore: otpStore,
|
otpStore: otpStore,
|
||||||
|
smsGateway: smsGateway,
|
||||||
|
emailGateway: emailGateway,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) CheckPhoneEmailExist(ctx context.Context, phoneNum, email string) (bool, bool, error) { // email,phone,error
|
|
||||||
return s.userStore.CheckPhoneEmailExist(ctx, phoneNum, email)
|
|
||||||
}
|
|
||||||
func (s *Service) SendRegisterCode(ctx context.Context, medium domain.OtpMedium, sentTo string) error {
|
|
||||||
var err error
|
|
||||||
// check if user exists
|
|
||||||
switch medium {
|
|
||||||
case domain.OtpMediumEmail:
|
|
||||||
_, err = s.userStore.GetUserByEmail(ctx, sentTo)
|
|
||||||
case domain.OtpMediumSms:
|
|
||||||
_, err = s.userStore.GetUserByPhone(ctx, sentTo)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// send otp based on the medium
|
|
||||||
return s.SendOtp(ctx, sentTo, domain.OtpReset, medium)
|
|
||||||
}
|
|
||||||
func (s *Service) RegisterUser(ctx context.Context, registerReq domain.RegisterUserReq) (domain.User, error) { // normal
|
|
||||||
// get otp
|
|
||||||
var sentTo string
|
|
||||||
if registerReq.OtpMedium == domain.OtpMediumEmail {
|
|
||||||
sentTo = registerReq.Email
|
|
||||||
} else {
|
|
||||||
sentTo = registerReq.PhoneNumber
|
|
||||||
}
|
|
||||||
//
|
|
||||||
otp, err := s.otpStore.GetOtp(
|
|
||||||
ctx, sentTo,
|
|
||||||
domain.OtpRegister, registerReq.OtpMedium)
|
|
||||||
if err != nil {
|
|
||||||
return domain.User{}, err
|
|
||||||
}
|
|
||||||
// verify otp
|
|
||||||
if otp.Used {
|
|
||||||
return domain.User{}, ErrOtpAlreadyUsed
|
|
||||||
}
|
|
||||||
if time.Now().After(otp.ExpiresAt) {
|
|
||||||
return domain.User{}, ErrOtpExpired
|
|
||||||
}
|
|
||||||
if otp.Otp != registerReq.Otp {
|
|
||||||
return domain.User{}, ErrInvalidOtp
|
|
||||||
}
|
|
||||||
|
|
||||||
hashedPassword, err := hashPassword(registerReq.Password)
|
|
||||||
if err != nil {
|
|
||||||
return domain.User{}, err
|
|
||||||
}
|
|
||||||
userR := domain.User{
|
|
||||||
FirstName: registerReq.FirstName,
|
|
||||||
LastName: registerReq.LastName,
|
|
||||||
Email: registerReq.Email,
|
|
||||||
PhoneNumber: registerReq.PhoneNumber,
|
|
||||||
Password: hashedPassword,
|
|
||||||
Role: "user",
|
|
||||||
EmailVerified: registerReq.OtpMedium == domain.OtpMediumEmail,
|
|
||||||
PhoneVerified: registerReq.OtpMedium == domain.OtpMediumSms,
|
|
||||||
}
|
|
||||||
// create the user and mark otp as used
|
|
||||||
user, err := s.userStore.CreateUser(ctx, userR, otp.ID)
|
|
||||||
if err != nil {
|
|
||||||
return domain.User{}, err
|
|
||||||
}
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) SendResetCode(ctx context.Context, medium domain.OtpMedium, sentTo string) error {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
// check if user exists
|
|
||||||
switch medium {
|
|
||||||
case domain.OtpMediumEmail:
|
|
||||||
_, err = s.userStore.GetUserByEmail(ctx, sentTo)
|
|
||||||
case domain.OtpMediumSms:
|
|
||||||
_, err = s.userStore.GetUserByPhone(ctx, sentTo)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.SendOtp(ctx, sentTo, domain.OtpReset, medium)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) ResetPassword(ctx context.Context, resetReq domain.ResetPasswordReq) error {
|
|
||||||
var sentTo string
|
|
||||||
if resetReq.OtpMedium == domain.OtpMediumEmail {
|
|
||||||
sentTo = resetReq.Email
|
|
||||||
} else {
|
|
||||||
sentTo = resetReq.PhoneNumber
|
|
||||||
}
|
|
||||||
otp, err := s.otpStore.GetOtp(
|
|
||||||
ctx, sentTo,
|
|
||||||
domain.OtpRegister, resetReq.OtpMedium)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
//
|
|
||||||
if otp.Used {
|
|
||||||
return ErrOtpAlreadyUsed
|
|
||||||
}
|
|
||||||
if time.Now().After(otp.ExpiresAt) {
|
|
||||||
return ErrOtpExpired
|
|
||||||
}
|
|
||||||
if otp.Otp != resetReq.Otp {
|
|
||||||
return ErrInvalidOtp
|
|
||||||
}
|
|
||||||
// hash password
|
|
||||||
hashedPassword, err := hashPassword(resetReq.Password)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// reset pass and mark otp as used
|
|
||||||
err = s.userStore.UpdatePassword(ctx, sentTo, hashedPassword, otp.ID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
func (s *Service) SendOtp(ctx context.Context, sentTo string, otpFor domain.OtpFor, medium domain.OtpMedium) error {
|
|
||||||
otpCode := "123456" // Generate OTP code
|
|
||||||
|
|
||||||
otp := domain.Otp{
|
|
||||||
SentTo: sentTo,
|
|
||||||
Medium: medium,
|
|
||||||
For: otpFor,
|
|
||||||
Otp: otpCode,
|
|
||||||
Used: false,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
ExpiresAt: time.Now().Add(OtpExpiry),
|
|
||||||
}
|
|
||||||
|
|
||||||
err := s.otpStore.CreateOtp(ctx, otp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch medium {
|
|
||||||
case domain.OtpMediumSms:
|
|
||||||
return s.smsGateway.SendSMSOTP(ctx, sentTo, otpCode)
|
|
||||||
case domain.OtpMediumEmail:
|
|
||||||
return s.emailGateway.SendEmailOTP(ctx, sentTo, otpCode)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error {
|
|
||||||
// update user
|
|
||||||
return s.userStore.UpdateUser(ctx, user)
|
|
||||||
|
|
||||||
}
|
|
||||||
func (s *Service) GetUserByID(ctx context.Context, id int64) (domain.User, error) {
|
|
||||||
return s.userStore.GetUserByID(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func hashPassword(plaintextPassword string) ([]byte, error) {
|
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(plaintextPassword), 12)
|
|
||||||
if err != nil {
|
|
||||||
return []byte{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
16
internal/services/user/user.go
Normal file
16
internal/services/user/user.go
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Service) UpdateUser(ctx context.Context, user domain.UpdateUserReq) error {
|
||||||
|
// update user
|
||||||
|
return s.userStore.UpdateUser(ctx, user)
|
||||||
|
|
||||||
|
}
|
||||||
|
func (s *Service) GetUserByID(ctx context.Context, id int64) (domain.User, error) {
|
||||||
|
return s.userStore.GetUserByID(ctx, id)
|
||||||
|
}
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||||
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||||
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
||||||
"github.com/bytedance/sonic"
|
"github.com/bytedance/sonic"
|
||||||
|
|
@ -16,6 +17,7 @@ type App struct {
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
port int
|
port int
|
||||||
authSvc *authentication.Service
|
authSvc *authentication.Service
|
||||||
|
userSvc *user.Service
|
||||||
validator *customvalidator.CustomValidator
|
validator *customvalidator.CustomValidator
|
||||||
JwtConfig jwtutil.JwtConfig
|
JwtConfig jwtutil.JwtConfig
|
||||||
}
|
}
|
||||||
|
|
@ -25,6 +27,7 @@ func NewApp(
|
||||||
authSvc *authentication.Service,
|
authSvc *authentication.Service,
|
||||||
logger *slog.Logger,
|
logger *slog.Logger,
|
||||||
JwtConfig jwtutil.JwtConfig,
|
JwtConfig jwtutil.JwtConfig,
|
||||||
|
userSvc *user.Service,
|
||||||
) *App {
|
) *App {
|
||||||
app := fiber.New(fiber.Config{
|
app := fiber.New(fiber.Config{
|
||||||
CaseSensitive: true,
|
CaseSensitive: true,
|
||||||
|
|
@ -39,6 +42,7 @@ func NewApp(
|
||||||
validator: validator,
|
validator: validator,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
JwtConfig: JwtConfig,
|
JwtConfig: JwtConfig,
|
||||||
|
userSvc: userSvc,
|
||||||
}
|
}
|
||||||
|
|
||||||
s.initAppRoutes()
|
s.initAppRoutes()
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||||
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||||
|
|
@ -66,7 +65,7 @@ func LoginCustomer(
|
||||||
return nil
|
return nil
|
||||||
|
|
||||||
}
|
}
|
||||||
accessToken, err := jwtutil.CreateJwt(strconv.Itoa(int(successRes.UserId)), successRes.Role, JwtConfig.JwtAccessKey, JwtConfig.JwtAccessExpiry)
|
accessToken, err := jwtutil.CreateJwt(successRes.UserId, successRes.Role, JwtConfig.JwtAccessKey, JwtConfig.JwtAccessExpiry)
|
||||||
res := loginCustomerRes{
|
res := loginCustomerRes{
|
||||||
AccessToken: accessToken,
|
AccessToken: accessToken,
|
||||||
RefreshToken: successRes.RfToken,
|
RefreshToken: successRes.RfToken,
|
||||||
|
|
@ -119,7 +118,7 @@ func RefreshToken(logger *slog.Logger, authSvc *authentication.Service,
|
||||||
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
|
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
accessToken, err := jwtutil.CreateJwt("", "", JwtConfig.JwtAccessKey, JwtConfig.JwtAccessExpiry)
|
accessToken, err := jwtutil.CreateJwt(0, "", JwtConfig.JwtAccessKey, JwtConfig.JwtAccessExpiry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Create jwt failed", "error", err)
|
logger.Error("Create jwt failed", "error", err)
|
||||||
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
|
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
|
||||||
|
|
|
||||||
365
internal/web_server/handlers/user.go
Normal file
365
internal/web_server/handlers/user.go
Normal file
|
|
@ -0,0 +1,365 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/user"
|
||||||
|
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||||
|
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CheckPhoneEmailExistReq struct {
|
||||||
|
Email string `json:"email" example:"john.doe@example.com"`
|
||||||
|
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
||||||
|
}
|
||||||
|
type CheckPhoneEmailExistRes struct {
|
||||||
|
EmailExist bool `json:"email_exist"`
|
||||||
|
PhoneNumberExist bool `json:"phone_number_exist"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckPhoneEmailExist godoc
|
||||||
|
// @Summary Check if phone number or email exist
|
||||||
|
// @Description Check if phone number or email exist
|
||||||
|
// @Tags user
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param checkPhoneEmailExist body CheckPhoneEmailExistReq true "Check phone number or email exist"
|
||||||
|
// @Success 200 {object} CheckPhoneEmailExistRes
|
||||||
|
// @Failure 400 {object} response.APIResponse
|
||||||
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
// @Router /user/checkPhoneEmailExist [post]
|
||||||
|
func CheckPhoneEmailExist(logger *slog.Logger, userSvc *user.Service,
|
||||||
|
validator *customvalidator.CustomValidator) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
var req CheckPhoneEmailExistReq
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
logger.Error("CheckPhoneEmailExist failed", "error", err)
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": "Invalid request",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
valErrs, ok := validator.Validate(c, req)
|
||||||
|
if !ok {
|
||||||
|
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
emailExist, phoneExist, err := userSvc.CheckPhoneEmailExist(c.Context(), req.PhoneNumber, req.Email)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("CheckPhoneEmailExist failed", "error", err)
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Internal server error",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
res := CheckPhoneEmailExistRes{
|
||||||
|
EmailExist: emailExist,
|
||||||
|
PhoneNumberExist: phoneExist,
|
||||||
|
}
|
||||||
|
return response.WriteJSON(c, fiber.StatusOK, "Check Success", res, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegisterCodeReq struct {
|
||||||
|
Email string `json:"email" example:"john.doe@example.com"`
|
||||||
|
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendRegisterCode godoc
|
||||||
|
// @Summary Send register code
|
||||||
|
// @Description Send register code
|
||||||
|
// @Tags user
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param registerCode body RegisterCodeReq true "Send register code"
|
||||||
|
// @Success 200 {object} response.APIResponse
|
||||||
|
// @Failure 400 {object} response.APIResponse
|
||||||
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
// @Router /user/sendRegisterCode [post]
|
||||||
|
func SendRegisterCode(logger *slog.Logger, userSvc *user.Service,
|
||||||
|
validator *customvalidator.CustomValidator) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
var req RegisterCodeReq
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": "Invalid request",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
valErrs, ok := validator.Validate(c, req)
|
||||||
|
if !ok {
|
||||||
|
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var sentTo string
|
||||||
|
var medium domain.OtpMedium
|
||||||
|
if req.Email != "" {
|
||||||
|
sentTo = req.Email
|
||||||
|
medium = domain.OtpMediumEmail
|
||||||
|
}
|
||||||
|
if req.PhoneNumber != "" {
|
||||||
|
sentTo = req.PhoneNumber
|
||||||
|
medium = domain.OtpMediumSms
|
||||||
|
}
|
||||||
|
if err := userSvc.SendRegisterCode(c.Context(), medium, sentTo); err != nil {
|
||||||
|
logger.Error("SendRegisterCode failed", "error", err)
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Internal server error",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RegisterUserReq 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"`
|
||||||
|
//Role string
|
||||||
|
Otp string `json:"otp" example:"123456"`
|
||||||
|
ReferalCode string `json:"referal_code" example:"ABC123"`
|
||||||
|
//
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterUser godoc
|
||||||
|
// @Summary Register user
|
||||||
|
// @Description Register user
|
||||||
|
// @Tags user
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param registerUser body RegisterUserReq true "Register user"
|
||||||
|
// @Success 200 {object} response.APIResponse
|
||||||
|
// @Failure 400 {object} response.APIResponse
|
||||||
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
// @Router /user/register [post]
|
||||||
|
func RegisterUser(logger *slog.Logger, userSvc *user.Service,
|
||||||
|
validator *customvalidator.CustomValidator) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
var req RegisterUserReq
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
logger.Error("RegisterUser failed", "error", err)
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": "Invalid request",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
valErrs, ok := validator.Validate(c, req)
|
||||||
|
if !ok {
|
||||||
|
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
user := domain.RegisterUserReq{
|
||||||
|
FirstName: req.FirstName,
|
||||||
|
LastName: req.LastName,
|
||||||
|
Email: req.Email,
|
||||||
|
PhoneNumber: req.PhoneNumber,
|
||||||
|
Password: req.Password,
|
||||||
|
Otp: req.Otp,
|
||||||
|
ReferalCode: req.ReferalCode,
|
||||||
|
OtpMedium: domain.OtpMediumEmail,
|
||||||
|
}
|
||||||
|
medium, err := getMedium(req.Email, req.PhoneNumber)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("RegisterUser failed", "error", err)
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
user.OtpMedium = medium
|
||||||
|
if _, err := userSvc.RegisterUser(c.Context(), user); err != nil {
|
||||||
|
if errors.Is(err, domain.ErrOtpAlreadyUsed) {
|
||||||
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Otp already used", nil, nil)
|
||||||
|
}
|
||||||
|
if errors.Is(err, domain.ErrOtpExpired) {
|
||||||
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Otp expired", nil, nil)
|
||||||
|
}
|
||||||
|
if errors.Is(err, domain.ErrInvalidOtp) {
|
||||||
|
return response.WriteJSON(c, fiber.StatusBadRequest, "Invalid otp", nil, nil)
|
||||||
|
}
|
||||||
|
if errors.Is(err, domain.ErrOtpNotFound) {
|
||||||
|
return response.WriteJSON(c, fiber.StatusBadRequest, "User already exist", nil, nil)
|
||||||
|
}
|
||||||
|
logger.Error("RegisterUser failed", "error", err)
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Internal server error",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return response.WriteJSON(c, fiber.StatusOK, "Registration successful", nil, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResetCodeReq struct {
|
||||||
|
Email string `json:"email" example:"john.doe@example.com"`
|
||||||
|
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendResetCode godoc
|
||||||
|
// @Summary Send reset code
|
||||||
|
// @Description Send reset code
|
||||||
|
// @Tags user
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param resetCode body ResetCodeReq true "Send reset code"
|
||||||
|
// @Success 200 {object} response.APIResponse
|
||||||
|
// @Failure 400 {object} response.APIResponse
|
||||||
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
// @Router /user/sendResetCode [post]
|
||||||
|
func SendResetCode(logger *slog.Logger, userSvc *user.Service,
|
||||||
|
validator *customvalidator.CustomValidator) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
var req ResetCodeReq
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": "Invalid request",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
valErrs, ok := validator.Validate(c, req)
|
||||||
|
if !ok {
|
||||||
|
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var sentTo string
|
||||||
|
var medium domain.OtpMedium
|
||||||
|
if req.Email != "" {
|
||||||
|
sentTo = req.Email
|
||||||
|
medium = domain.OtpMediumEmail
|
||||||
|
}
|
||||||
|
if req.PhoneNumber != "" {
|
||||||
|
sentTo = req.PhoneNumber
|
||||||
|
medium = domain.OtpMediumSms
|
||||||
|
}
|
||||||
|
if err := userSvc.SendResetCode(c.Context(), medium, sentTo); err != nil {
|
||||||
|
logger.Error("SendResetCode failed", "error", err)
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Internal server error",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return response.WriteJSON(c, fiber.StatusOK, "Code sent successfully", nil, nil)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ResetPasswordReq struct {
|
||||||
|
Email string
|
||||||
|
PhoneNumber string
|
||||||
|
Password string
|
||||||
|
Otp string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetPassword godoc
|
||||||
|
// @Summary Reset password
|
||||||
|
// @Description Reset password
|
||||||
|
// @Tags user
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param resetPassword body ResetPasswordReq true "Reset password"
|
||||||
|
// @Success 200 {object} response.APIResponse
|
||||||
|
// @Failure 400 {object} response.APIResponse
|
||||||
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
// @Router /user/resetPassword [post]
|
||||||
|
func ResetPassword(logger *slog.Logger, userSvc *user.Service,
|
||||||
|
validator *customvalidator.CustomValidator) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
var req ResetPasswordReq
|
||||||
|
if err := c.BodyParser(&req); err != nil {
|
||||||
|
logger.Error("ResetPassword failed", "error", err)
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": "Invalid request",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
valErrs, ok := validator.Validate(c, req)
|
||||||
|
if !ok {
|
||||||
|
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
user := domain.ResetPasswordReq{
|
||||||
|
Email: req.Email,
|
||||||
|
PhoneNumber: req.PhoneNumber,
|
||||||
|
Password: req.Password,
|
||||||
|
Otp: req.Otp,
|
||||||
|
}
|
||||||
|
medium, err := getMedium(req.Email, req.PhoneNumber)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("ResetPassword failed", "error", err)
|
||||||
|
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
user.OtpMedium = medium
|
||||||
|
if err := userSvc.ResetPassword(c.Context(), user); err != nil {
|
||||||
|
logger.Error("ResetPassword failed", "error", err)
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Internal server error",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return response.WriteJSON(c, fiber.StatusOK, "Password reset successful", nil, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserProfileRes struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
PhoneNumber string `json:"phone_number"`
|
||||||
|
Role domain.Role `json:"role"`
|
||||||
|
EmailVerified bool `json:"email_verified"`
|
||||||
|
PhoneVerified bool `json:"phone_verified"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
SuspendedAt time.Time `json:"suspended_at"`
|
||||||
|
Suspended bool `json:"suspended"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserProfile godoc
|
||||||
|
// @Summary Get user profile
|
||||||
|
// @Description Get user profile
|
||||||
|
// @Tags user
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {object} UserProfileRes
|
||||||
|
// @Failure 400 {object} response.APIResponse
|
||||||
|
// @Failure 500 {object} response.APIResponse
|
||||||
|
// @Security Bearer
|
||||||
|
// @Router /user/profile [get]
|
||||||
|
func UserProfile(logger *slog.Logger, userSvc *user.Service) fiber.Handler {
|
||||||
|
return func(c *fiber.Ctx) error {
|
||||||
|
userId := c.Locals("user_id").(int64)
|
||||||
|
user, err := userSvc.GetUserByID(c.Context(), userId)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("GetUserProfile failed", "error", err)
|
||||||
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||||
|
"error": "Internal server error",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
res := UserProfileRes{
|
||||||
|
ID: user.ID,
|
||||||
|
FirstName: user.FirstName,
|
||||||
|
LastName: user.LastName,
|
||||||
|
Email: user.Email,
|
||||||
|
PhoneNumber: user.PhoneNumber,
|
||||||
|
Role: user.Role,
|
||||||
|
EmailVerified: user.EmailVerified,
|
||||||
|
PhoneVerified: user.PhoneVerified,
|
||||||
|
CreatedAt: user.CreatedAt,
|
||||||
|
UpdatedAt: user.UpdatedAt,
|
||||||
|
SuspendedAt: user.SuspendedAt,
|
||||||
|
Suspended: user.Suspended,
|
||||||
|
}
|
||||||
|
return response.WriteJSON(c, fiber.StatusOK, "User profile retrieved successfully", res, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func getMedium(email, phoneNumber string) (domain.OtpMedium, error) {
|
||||||
|
if email != "" {
|
||||||
|
return domain.OtpMediumEmail, nil
|
||||||
|
}
|
||||||
|
if phoneNumber != "" {
|
||||||
|
return domain.OtpMediumSms, nil
|
||||||
|
}
|
||||||
|
return "", errors.New("both email and phone number are empty")
|
||||||
|
}
|
||||||
|
|
@ -20,7 +20,7 @@ var (
|
||||||
|
|
||||||
type UserClaim struct {
|
type UserClaim struct {
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
UserId string
|
UserId int64
|
||||||
Role domain.Role
|
Role domain.Role
|
||||||
}
|
}
|
||||||
type JwtConfig struct {
|
type JwtConfig struct {
|
||||||
|
|
@ -28,7 +28,7 @@ type JwtConfig struct {
|
||||||
JwtAccessExpiry int
|
JwtAccessExpiry int
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateJwt(userId string, Role domain.Role, key string, expiry int) (string, error) {
|
func CreateJwt(userId int64, Role domain.Role, key string, expiry int) (string, error) {
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{RegisteredClaims: jwt.RegisteredClaims{Issuer: "github.com/lafetz/snippitstash",
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{RegisteredClaims: jwt.RegisteredClaims{Issuer: "github.com/lafetz/snippitstash",
|
||||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
Audience: jwt.ClaimStrings{"fortune.com"},
|
Audience: jwt.ClaimStrings{"fortune.com"},
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ func (a *App) authMiddleware(c *fiber.Ctx) error {
|
||||||
|
|
||||||
// refreshToken = c.Cookies("refresh_token", "")
|
// refreshToken = c.Cookies("refresh_token", "")
|
||||||
|
|
||||||
return fiber.NewError(fiber.StatusUnauthorized, "Refresh token missing")
|
// return fiber.NewError(fiber.StatusUnauthorized, "Refresh token missing")
|
||||||
}
|
}
|
||||||
c.Locals("user_id", claim.UserId)
|
c.Locals("user_id", claim.UserId)
|
||||||
c.Locals("role", claim.Role)
|
c.Locals("role", claim.Role)
|
||||||
|
|
|
||||||
|
|
@ -20,5 +20,19 @@ func (a *App) initAppRoutes() {
|
||||||
a.logger.Info("Refresh Token: " + refreshToken.(string))
|
a.logger.Info("Refresh Token: " + refreshToken.(string))
|
||||||
return c.SendString("Test endpoint")
|
return c.SendString("Test endpoint")
|
||||||
})
|
})
|
||||||
|
a.fiber.Post("/user/resetPassword", handlers.ResetPassword(a.logger, a.userSvc, a.validator))
|
||||||
|
a.fiber.Post("/user/sendResetCode", handlers.SendResetCode(a.logger, a.userSvc, a.validator))
|
||||||
|
a.fiber.Post("/user/register", handlers.RegisterUser(a.logger, a.userSvc, a.validator))
|
||||||
|
a.fiber.Post("/user/sendRegisterCode", handlers.SendRegisterCode(a.logger, a.userSvc, a.validator))
|
||||||
|
a.fiber.Post("/user/checkPhoneEmailExist", handlers.CheckPhoneEmailExist(a.logger, a.userSvc, a.validator))
|
||||||
|
a.fiber.Get("/user/profile", a.authMiddleware, handlers.UserProfile(a.logger, a.userSvc))
|
||||||
|
// Swagger
|
||||||
a.fiber.Get("/swagger/*", fiberSwagger.WrapHandler)
|
a.fiber.Get("/swagger/*", fiberSwagger.WrapHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///user/profile get
|
||||||
|
// @Router /user/resetPassword [post]
|
||||||
|
// @Router /user/sendResetCode [post]
|
||||||
|
// @Router /user/register [post]
|
||||||
|
// @Router /user/sendRegisterCode [post]
|
||||||
|
// @Router /user/checkPhoneEmailExist [post]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user