impl auth http layer
This commit is contained in:
parent
e8f0e43836
commit
c8b215cc43
41
cmd/main.go
41
cmd/main.go
|
|
@ -6,16 +6,29 @@ import (
|
|||
"os"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/config"
|
||||
customlogger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/repository"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||
httpserver "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server"
|
||||
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
// @title FortuneBet API
|
||||
// @version 1.0
|
||||
// @description This is server for FortuneBet.
|
||||
// @termsOfService http://swagger.io/terms/
|
||||
// @contact.name API Support
|
||||
// @contact.url http://www.swagger.io/support
|
||||
// @contact.email support@swagger.io
|
||||
// @license.name Apache 2.0
|
||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
// @SecurityDefinitions.apiKey Bearer
|
||||
// @in header
|
||||
// @name Authorization
|
||||
// @BasePath /
|
||||
func main() {
|
||||
err := godotenv.Load()
|
||||
if err != nil {
|
||||
slog.Error(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg, err := config.NewConfig()
|
||||
if err != nil {
|
||||
slog.Error(err.Error())
|
||||
|
|
@ -23,9 +36,21 @@ func main() {
|
|||
}
|
||||
db, _, err := repository.OpenDB(cfg.DbUrl)
|
||||
if err != nil {
|
||||
fmt.Print(err)
|
||||
fmt.Print("db", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
logger := customlogger.NewLogger(cfg.Env, cfg.LogLevel)
|
||||
store := repository.NewStore(db)
|
||||
fmt.Println(store)
|
||||
v := customvalidator.NewCustomValidator(validator.New())
|
||||
authSvc := authentication.NewService(store, store, cfg.RefreshExpiry)
|
||||
app := httpserver.NewApp(cfg.Port, v, authSvc, logger, httpserver.JwtConfig{
|
||||
JwtAccessKey: cfg.JwtKey,
|
||||
JwtAccessExpiry: cfg.AccessExpiry,
|
||||
})
|
||||
logger.Info("Starting server", "port", cfg.Port)
|
||||
if err := app.Run(); err != nil {
|
||||
logger.Error("Failed to start server", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,32 @@ CREATE TABLE users (
|
|||
password BYTEA NOT NULL,
|
||||
role VARCHAR(50) NOT NULL,
|
||||
verified BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP ,
|
||||
updated_at TIMESTAMP
|
||||
created_at TIMESTAMPTZ ,
|
||||
updated_at TIMESTAMPTZ ,
|
||||
CONSTRAINT unique_email_phone UNIQUE (email, phone_number)
|
||||
);
|
||||
CREATE TABLE refresh_tokens (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL,
|
||||
token TEXT NOT NULL UNIQUE,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
revoked BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
CONSTRAINT unique_token UNIQUE (token)
|
||||
);
|
||||
----------------------------------------------seed data-------------------------------------------------------------
|
||||
-------------------------------------- DO NOT USE IN PRODUCTION-------------------------------------------------
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
INSERT INTO users (first_name, last_name, email, phone_number, password, role, created_at, updated_at)
|
||||
VALUES (
|
||||
'John',
|
||||
'Doe',
|
||||
'john.doe@example.com',
|
||||
'1234567890',
|
||||
crypt('password123', gen_salt('bf'))::bytea,
|
||||
'user',
|
||||
CURRENT_TIMESTAMP,
|
||||
CURRENT_TIMESTAMP
|
||||
);
|
||||
16
db/query/auth.sql
Normal file
16
db/query/auth.sql
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
-- name: GetUserByEmailPhone :one
|
||||
SELECT * FROM users
|
||||
WHERE email = $1 OR phone_number = $2;
|
||||
|
||||
-- name: CreateRefreshToken :exec
|
||||
INSERT INTO refresh_tokens (user_id, token, expires_at, created_at, revoked)
|
||||
VALUES ($1, $2, $3, $4, $5);
|
||||
|
||||
-- name: GetRefreshToken :one
|
||||
SELECT * FROM refresh_tokens
|
||||
WHERE token = $1;
|
||||
|
||||
-- name: RevokeRefreshToken :exec
|
||||
UPDATE refresh_tokens
|
||||
SET revoked = TRUE
|
||||
WHERE token = $1;
|
||||
285
docs/docs.go
Normal file
285
docs/docs.go
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
||||
package docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
|
||||
const docTemplate = `{
|
||||
"schemes": {{ marshal .Schemes }},
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "{{escape .Description}}",
|
||||
"title": "{{.Title}}",
|
||||
"termsOfService": "http://swagger.io/terms/",
|
||||
"contact": {
|
||||
"name": "API Support",
|
||||
"url": "http://www.swagger.io/support",
|
||||
"email": "support@swagger.io"
|
||||
},
|
||||
"license": {
|
||||
"name": "Apache 2.0",
|
||||
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
|
||||
},
|
||||
"version": "{{.Version}}"
|
||||
},
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/auth/login": {
|
||||
"post": {
|
||||
"description": "Login customer",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Login customer",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Login customer",
|
||||
"name": "login",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpserver.loginCustomerReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpserver.loginCustomerRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/logout": {
|
||||
"post": {
|
||||
"description": "Logout customer",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Logout customer",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Logout customer",
|
||||
"name": "logout",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpserver.logoutReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/refresh": {
|
||||
"post": {
|
||||
"description": "Refresh token",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Refresh token",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "tokens",
|
||||
"name": "refresh",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpserver.refreshToken"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpserver.loginCustomerRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"httpserver.loginCustomerReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"example": "john.doe@example.com"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"example": "password123"
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
}
|
||||
}
|
||||
},
|
||||
"httpserver.loginCustomerRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"access_token": {
|
||||
"type": "string"
|
||||
},
|
||||
"refresh_token": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"httpserver.logoutReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"refresh_token": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"httpserver.refreshToken": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"access_token": {
|
||||
"type": "string"
|
||||
},
|
||||
"refresh_token": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"response.APIResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {},
|
||||
"status": {
|
||||
"$ref": "#/definitions/response.Status"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"response.Status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"error",
|
||||
"success"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"Error",
|
||||
"Success"
|
||||
]
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
"Bearer": {
|
||||
"type": "apiKey",
|
||||
"name": "Authorization",
|
||||
"in": "header"
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = &swag.Spec{
|
||||
Version: "1.0",
|
||||
Host: "",
|
||||
BasePath: "",
|
||||
Schemes: []string{},
|
||||
Title: "FortuneBet API",
|
||||
Description: "This is server for FortuneBet.",
|
||||
InfoInstanceName: "swagger",
|
||||
SwaggerTemplate: docTemplate,
|
||||
LeftDelim: "{{",
|
||||
RightDelim: "}}",
|
||||
}
|
||||
|
||||
func init() {
|
||||
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||
}
|
||||
259
docs/swagger.json
Normal file
259
docs/swagger.json
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "This is server for FortuneBet.",
|
||||
"title": "FortuneBet API",
|
||||
"termsOfService": "http://swagger.io/terms/",
|
||||
"contact": {
|
||||
"name": "API Support",
|
||||
"url": "http://www.swagger.io/support",
|
||||
"email": "support@swagger.io"
|
||||
},
|
||||
"license": {
|
||||
"name": "Apache 2.0",
|
||||
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
|
||||
},
|
||||
"version": "1.0"
|
||||
},
|
||||
"paths": {
|
||||
"/auth/login": {
|
||||
"post": {
|
||||
"description": "Login customer",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Login customer",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Login customer",
|
||||
"name": "login",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpserver.loginCustomerReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpserver.loginCustomerRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/logout": {
|
||||
"post": {
|
||||
"description": "Logout customer",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Logout customer",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Logout customer",
|
||||
"name": "logout",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpserver.logoutReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/auth/refresh": {
|
||||
"post": {
|
||||
"description": "Refresh token",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"auth"
|
||||
],
|
||||
"summary": "Refresh token",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "tokens",
|
||||
"name": "refresh",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpserver.refreshToken"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpserver.loginCustomerRes"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/response.APIResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"httpserver.loginCustomerReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string",
|
||||
"example": "john.doe@example.com"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"example": "password123"
|
||||
},
|
||||
"phone_number": {
|
||||
"type": "string",
|
||||
"example": "1234567890"
|
||||
}
|
||||
}
|
||||
},
|
||||
"httpserver.loginCustomerRes": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"access_token": {
|
||||
"type": "string"
|
||||
},
|
||||
"refresh_token": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"httpserver.logoutReq": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"refresh_token": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"httpserver.refreshToken": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"access_token": {
|
||||
"type": "string"
|
||||
},
|
||||
"refresh_token": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"response.APIResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {},
|
||||
"status": {
|
||||
"$ref": "#/definitions/response.Status"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"response.Status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"error",
|
||||
"success"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"Error",
|
||||
"Success"
|
||||
]
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
"Bearer": {
|
||||
"type": "apiKey",
|
||||
"name": "Authorization",
|
||||
"in": "header"
|
||||
}
|
||||
}
|
||||
}
|
||||
172
docs/swagger.yaml
Normal file
172
docs/swagger.yaml
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
definitions:
|
||||
httpserver.loginCustomerReq:
|
||||
properties:
|
||||
email:
|
||||
example: john.doe@example.com
|
||||
type: string
|
||||
password:
|
||||
example: password123
|
||||
type: string
|
||||
phone_number:
|
||||
example: "1234567890"
|
||||
type: string
|
||||
type: object
|
||||
httpserver.loginCustomerRes:
|
||||
properties:
|
||||
access_token:
|
||||
type: string
|
||||
refresh_token:
|
||||
type: string
|
||||
type: object
|
||||
httpserver.logoutReq:
|
||||
properties:
|
||||
refresh_token:
|
||||
type: string
|
||||
type: object
|
||||
httpserver.refreshToken:
|
||||
properties:
|
||||
access_token:
|
||||
type: string
|
||||
refresh_token:
|
||||
type: string
|
||||
type: object
|
||||
response.APIResponse:
|
||||
properties:
|
||||
data: {}
|
||||
message:
|
||||
type: string
|
||||
metadata: {}
|
||||
status:
|
||||
$ref: '#/definitions/response.Status'
|
||||
timestamp:
|
||||
type: string
|
||||
type: object
|
||||
response.Status:
|
||||
enum:
|
||||
- error
|
||||
- success
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- Error
|
||||
- Success
|
||||
info:
|
||||
contact:
|
||||
email: support@swagger.io
|
||||
name: API Support
|
||||
url: http://www.swagger.io/support
|
||||
description: This is server for FortuneBet.
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
termsOfService: http://swagger.io/terms/
|
||||
title: FortuneBet API
|
||||
version: "1.0"
|
||||
paths:
|
||||
/auth/login:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Login customer
|
||||
parameters:
|
||||
- description: Login customer
|
||||
in: body
|
||||
name: login
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/httpserver.loginCustomerReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/httpserver.loginCustomerRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Login customer
|
||||
tags:
|
||||
- auth
|
||||
/auth/logout:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Logout customer
|
||||
parameters:
|
||||
- description: Logout customer
|
||||
in: body
|
||||
name: logout
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/httpserver.logoutReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Logout customer
|
||||
tags:
|
||||
- auth
|
||||
/auth/refresh:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Refresh token
|
||||
parameters:
|
||||
- description: tokens
|
||||
in: body
|
||||
name: refresh
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/httpserver.refreshToken'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/httpserver.loginCustomerRes'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/response.APIResponse'
|
||||
summary: Refresh token
|
||||
tags:
|
||||
- auth
|
||||
securityDefinitions:
|
||||
Bearer:
|
||||
in: header
|
||||
name: Authorization
|
||||
type: apiKey
|
||||
swagger: "2.0"
|
||||
94
gen/db/auth.sql.go
Normal file
94
gen/db/auth.sql.go
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// source: auth.sql
|
||||
|
||||
package dbgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const CreateRefreshToken = `-- name: CreateRefreshToken :exec
|
||||
INSERT INTO refresh_tokens (user_id, token, expires_at, created_at, revoked)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
`
|
||||
|
||||
type CreateRefreshTokenParams struct {
|
||||
UserID int64
|
||||
Token string
|
||||
ExpiresAt pgtype.Timestamptz
|
||||
CreatedAt pgtype.Timestamptz
|
||||
Revoked bool
|
||||
}
|
||||
|
||||
func (q *Queries) CreateRefreshToken(ctx context.Context, arg CreateRefreshTokenParams) error {
|
||||
_, err := q.db.Exec(ctx, CreateRefreshToken,
|
||||
arg.UserID,
|
||||
arg.Token,
|
||||
arg.ExpiresAt,
|
||||
arg.CreatedAt,
|
||||
arg.Revoked,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const GetRefreshToken = `-- name: GetRefreshToken :one
|
||||
SELECT id, user_id, token, expires_at, created_at, revoked FROM refresh_tokens
|
||||
WHERE token = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetRefreshToken(ctx context.Context, token string) (RefreshToken, error) {
|
||||
row := q.db.QueryRow(ctx, GetRefreshToken, token)
|
||||
var i RefreshToken
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.UserID,
|
||||
&i.Token,
|
||||
&i.ExpiresAt,
|
||||
&i.CreatedAt,
|
||||
&i.Revoked,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const GetUserByEmailPhone = `-- name: GetUserByEmailPhone :one
|
||||
SELECT id, first_name, last_name, email, phone_number, password, role, verified, created_at, updated_at FROM users
|
||||
WHERE email = $1 OR phone_number = $2
|
||||
`
|
||||
|
||||
type GetUserByEmailPhoneParams struct {
|
||||
Email string
|
||||
PhoneNumber string
|
||||
}
|
||||
|
||||
func (q *Queries) GetUserByEmailPhone(ctx context.Context, arg GetUserByEmailPhoneParams) (User, error) {
|
||||
row := q.db.QueryRow(ctx, GetUserByEmailPhone, arg.Email, arg.PhoneNumber)
|
||||
var i User
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.FirstName,
|
||||
&i.LastName,
|
||||
&i.Email,
|
||||
&i.PhoneNumber,
|
||||
&i.Password,
|
||||
&i.Role,
|
||||
&i.Verified,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const RevokeRefreshToken = `-- name: RevokeRefreshToken :exec
|
||||
UPDATE refresh_tokens
|
||||
SET revoked = TRUE
|
||||
WHERE token = $1
|
||||
`
|
||||
|
||||
func (q *Queries) RevokeRefreshToken(ctx context.Context, token string) error {
|
||||
_, err := q.db.Exec(ctx, RevokeRefreshToken, token)
|
||||
return err
|
||||
}
|
||||
|
|
@ -8,6 +8,15 @@ import (
|
|||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type RefreshToken struct {
|
||||
ID int64
|
||||
UserID int64
|
||||
Token string
|
||||
ExpiresAt pgtype.Timestamptz
|
||||
CreatedAt pgtype.Timestamptz
|
||||
Revoked bool
|
||||
}
|
||||
|
||||
type User struct {
|
||||
ID int64
|
||||
FirstName string
|
||||
|
|
@ -17,6 +26,6 @@ type User struct {
|
|||
Password []byte
|
||||
Role string
|
||||
Verified pgtype.Bool
|
||||
CreatedAt pgtype.Timestamp
|
||||
UpdatedAt pgtype.Timestamp
|
||||
CreatedAt pgtype.Timestamptz
|
||||
UpdatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
|
|
|||
40
go.mod
40
go.mod
|
|
@ -4,39 +4,51 @@ go 1.24.1
|
|||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.13.2
|
||||
github.com/go-playground/validator/v10 v10.26.0
|
||||
github.com/gofiber/fiber/v2 v2.52.6
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/jackc/pgx/v5 v5.7.4
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/crypto v0.32.0
|
||||
github.com/swaggo/fiber-swagger v1.3.0
|
||||
github.com/swaggo/swag v1.16.4
|
||||
golang.org/x/crypto v0.36.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/andybalholm/brotli v1.1.1 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/klauspost/compress v1.17.9 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rogpeppe/go-internal v1.14.1 // indirect
|
||||
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.59.0 // indirect
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
golang.org/x/tools v0.31.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
|
|||
165
go.sum
165
go.sum
|
|
@ -1,5 +1,12 @@
|
|||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
|
||||
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ=
|
||||
github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
|
|
@ -8,16 +15,44 @@ github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFos
|
|||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic=
|
||||
github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk=
|
||||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
|
||||
github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU=
|
||||
github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/gofiber/fiber/v2 v2.32.0/go.mod h1:CMy5ZLiXkn6qwthrl03YMyW1NLfj0rhxz2LKl4t7ZTY=
|
||||
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
|
||||
github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
|
|
@ -28,62 +63,144 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
|
|||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
|
||||
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U=
|
||||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
|
||||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs=
|
||||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
|
||||
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/swaggo/fiber-swagger v1.3.0 h1:RMjIVDleQodNVdKuu7GRs25Eq8RVXK7MwY9f5jbobNg=
|
||||
github.com/swaggo/fiber-swagger v1.3.0/go.mod h1:18MuDqBkYEiUmeM/cAAB8CI28Bi62d/mys39j1QqF9w=
|
||||
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc=
|
||||
github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
|
||||
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
|
||||
github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
|
||||
github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/fasthttp v1.35.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
|
||||
github.com/valyala/fasthttp v1.36.0/go.mod h1:t/G+3rLek+CyY9bnIE+YlMRddxVAAGjhxndDB4i4C0I=
|
||||
github.com/valyala/fasthttp v1.59.0 h1:Qu0qYHfXvPk1mSLNqcFtEk6DpxgA26hy6bmydotDpRI=
|
||||
github.com/valyala/fasthttp v1.59.0/go.mod h1:GTxNb9Bc6r2a9D0TWNSPwDz78UxnTGBViY3xZNEqyYU=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
|
||||
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
|
|
|
|||
|
|
@ -2,18 +2,33 @@ package config
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
customlogger "github.com/SamuelTariku/FortuneBet-Backend/internal/logger"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidDbUrl = errors.New("db url is invalid")
|
||||
ErrInvalidPort = errors.New("port number is invalid")
|
||||
ErrInvalidDbUrl = errors.New("db url is invalid")
|
||||
ErrInvalidPort = errors.New("port number is invalid")
|
||||
ErrRefreshExpiry = errors.New("refresh token expiry is invalid")
|
||||
ErrAccessExpiry = errors.New("access token expiry is invalid")
|
||||
ErrInvalidJwtKey = errors.New("jwt key is invalid")
|
||||
ErrLogLevel = errors.New("log level not set")
|
||||
ErrInvalidLevel = errors.New("invalid log level")
|
||||
ErrInvalidEnv = errors.New("env not set or invalid")
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Port int
|
||||
DbUrl string
|
||||
Port int
|
||||
DbUrl string
|
||||
RefreshExpiry int
|
||||
AccessExpiry int
|
||||
JwtKey string
|
||||
LogLevel slog.Level
|
||||
Env string
|
||||
}
|
||||
|
||||
func NewConfig() (*Config, error) {
|
||||
|
|
@ -24,7 +39,16 @@ func NewConfig() (*Config, error) {
|
|||
return config, nil
|
||||
}
|
||||
func (c *Config) loadEnv() error {
|
||||
|
||||
err := godotenv.Load()
|
||||
if err != nil {
|
||||
return errors.New("failed to load env file")
|
||||
}
|
||||
// env
|
||||
env := os.Getenv("ENV")
|
||||
if env == "" {
|
||||
return ErrInvalidEnv
|
||||
}
|
||||
c.Env = env
|
||||
portStr := os.Getenv("PORT")
|
||||
port, err := strconv.Atoi(portStr)
|
||||
if err != nil {
|
||||
|
|
@ -37,6 +61,33 @@ func (c *Config) loadEnv() error {
|
|||
return ErrInvalidDbUrl
|
||||
}
|
||||
c.DbUrl = dbUrl
|
||||
refreshExpiryStr := os.Getenv("REFRESH_EXPIRY")
|
||||
refreshExpiry, err := strconv.Atoi(refreshExpiryStr)
|
||||
if err != nil {
|
||||
return ErrRefreshExpiry
|
||||
}
|
||||
c.RefreshExpiry = refreshExpiry
|
||||
jwtKey := os.Getenv("JWT_KEY")
|
||||
if jwtKey == "" {
|
||||
return ErrInvalidJwtKey
|
||||
}
|
||||
c.JwtKey = jwtKey
|
||||
accessExpiryStr := os.Getenv("ACCESS_EXPIRY")
|
||||
accessExpiry, err := strconv.Atoi(accessExpiryStr)
|
||||
if err != nil {
|
||||
return ErrAccessExpiry
|
||||
}
|
||||
c.AccessExpiry = accessExpiry
|
||||
// log level
|
||||
logLevel := os.Getenv("LOG_LEVEL")
|
||||
if logLevel == "" {
|
||||
return ErrLogLevel
|
||||
}
|
||||
|
||||
lvl, ok := customlogger.LogLevels[logLevel]
|
||||
if !ok {
|
||||
return ErrInvalidLevel
|
||||
}
|
||||
c.LogLevel = lvl
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,8 +11,12 @@ var LogLevels = map[string]slog.Level{
|
|||
"warn": slog.LevelWarn,
|
||||
"error": slog.LevelError,
|
||||
}
|
||||
var Environment = map[string]string{
|
||||
"dev": "development",
|
||||
"prod": "production",
|
||||
}
|
||||
|
||||
func NewLogger(env string, lvl slog.Level, version string) *slog.Logger {
|
||||
func NewLogger(env string, lvl slog.Level) *slog.Logger {
|
||||
var logHandler slog.Handler
|
||||
switch env {
|
||||
case "development":
|
||||
|
|
@ -28,7 +32,6 @@ func NewLogger(env string, lvl slog.Level, version string) *slog.Logger {
|
|||
logger := slog.New(logHandler).With(slog.Group(
|
||||
"service_info",
|
||||
slog.String("env", env),
|
||||
slog.String("version", version),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
71
internal/repository/auth.go
Normal file
71
internal/repository/auth.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
|
||||
dbgen "github.com/SamuelTariku/FortuneBet-Backend/gen/db"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/domain"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
func (s *Store) GetUserByEmailPhone(ctx context.Context, email, phone string) (domain.User, error) {
|
||||
user, err := s.queries.GetUserByEmailPhone(ctx, dbgen.GetUserByEmailPhoneParams{
|
||||
Email: email,
|
||||
PhoneNumber: phone,
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return domain.User{}, authentication.ErrUserNotFound
|
||||
}
|
||||
return domain.User{}, err
|
||||
}
|
||||
return domain.User{
|
||||
ID: user.ID,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
Email: user.Email,
|
||||
PhoneNumber: user.PhoneNumber,
|
||||
Password: user.Password,
|
||||
Role: user.Role,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (s *Store) CreateRefreshToken(ctx context.Context, rt domain.RefreshToken) error {
|
||||
return s.queries.CreateRefreshToken(ctx, dbgen.CreateRefreshTokenParams{
|
||||
UserID: rt.UserID,
|
||||
Token: rt.Token,
|
||||
CreatedAt: pgtype.Timestamptz{
|
||||
Time: rt.CreatedAt,
|
||||
Valid: true,
|
||||
},
|
||||
ExpiresAt: pgtype.Timestamptz{
|
||||
Time: rt.ExpiresAt,
|
||||
Valid: true,
|
||||
},
|
||||
Revoked: rt.Revoked,
|
||||
})
|
||||
|
||||
}
|
||||
func (s *Store) GetRefreshToken(ctx context.Context, token string) (domain.RefreshToken, error) {
|
||||
rf, err := s.queries.GetRefreshToken(ctx, token)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return domain.RefreshToken{}, authentication.ErrRefreshTokenNotFound
|
||||
}
|
||||
return domain.RefreshToken{}, err
|
||||
}
|
||||
return domain.RefreshToken{
|
||||
Token: rf.Token,
|
||||
UserID: rf.UserID,
|
||||
CreatedAt: rf.CreatedAt.Time,
|
||||
ExpiresAt: rf.ExpiresAt.Time,
|
||||
Revoked: rf.Revoked,
|
||||
}, nil
|
||||
}
|
||||
func (s *Store) RevokeRefreshToken(ctx context.Context, token string) error {
|
||||
return s.queries.RevokeRefreshToken(ctx, token)
|
||||
}
|
||||
|
|
@ -18,23 +18,25 @@ var (
|
|||
ErrRefreshTokenNotFound = errors.New("refresh token not found") // i.e login again
|
||||
)
|
||||
|
||||
func (s *Service) Login(ctx context.Context, emailPhone EmailPhone, password string) (string, error) {
|
||||
user, err := s.userStore.GetUserByEmailPhone(ctx, emailPhone)
|
||||
type LoginSuccess struct {
|
||||
UserId int64
|
||||
Role string
|
||||
RfToken string
|
||||
}
|
||||
|
||||
func (s *Service) Login(ctx context.Context, email, phone string, password string) (LoginSuccess, error) {
|
||||
user, err := s.userStore.GetUserByEmailPhone(ctx, email, phone)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return LoginSuccess{}, err
|
||||
}
|
||||
err = matchPassword(password, user.Password)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return LoginSuccess{}, err
|
||||
}
|
||||
// //create session
|
||||
// accessToken, err := CreateJwt(strconv.Itoa(int(user.ID)), s.jwtConfig.JwtAccessKey, s.jwtConfig.JwtAccessExpiry)
|
||||
// if err != nil {
|
||||
// return Tokens{}, err
|
||||
// }
|
||||
|
||||
refreshToken, err := generateRefreshToken()
|
||||
if err != nil {
|
||||
return "", err
|
||||
return LoginSuccess{}, err
|
||||
}
|
||||
err = s.tokenStore.CreateRefreshToken(ctx, domain.RefreshToken{
|
||||
Token: refreshToken,
|
||||
|
|
@ -43,19 +45,17 @@ func (s *Service) Login(ctx context.Context, emailPhone EmailPhone, password str
|
|||
ExpiresAt: time.Now().Add(time.Duration(s.RefreshExpiry) * time.Second),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
return LoginSuccess{}, err
|
||||
}
|
||||
return refreshToken, nil
|
||||
return LoginSuccess{
|
||||
UserId: user.ID,
|
||||
Role: user.Role,
|
||||
RfToken: refreshToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) RefreshToken(ctx context.Context, refToken string) (string, error) {
|
||||
// us, err := ParseJwt(tokens.RefreshToken, s.jwtConfig.JwtAccessKey)
|
||||
// if err == nil {
|
||||
// return Tokens{}, err
|
||||
// }
|
||||
// if !errors.Is(err, ErrExpiredToken) {
|
||||
// return Tokens{}, err
|
||||
// }
|
||||
|
||||
token, err := s.tokenStore.GetRefreshToken(ctx, refToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
|
@ -66,21 +66,12 @@ func (s *Service) RefreshToken(ctx context.Context, refToken string) (string, er
|
|||
if token.ExpiresAt.Before(time.Now()) {
|
||||
return "", ErrExpiredToken
|
||||
}
|
||||
//
|
||||
// naccessToken, err := CreateJwt(token., s.jwtConfig.JwtAccessKey, s.jwtConfig.JwtAccessExpiry)
|
||||
// if err != nil {
|
||||
// return Tokens{}, err
|
||||
// }
|
||||
|
||||
newRefToken, err := generateRefreshToken()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// ntokens := Tokens{
|
||||
// AccessToken: naccessToken,
|
||||
// RefreshToken: nrefreshToken,
|
||||
// }
|
||||
|
||||
err = s.tokenStore.CreateRefreshToken(ctx, domain.RefreshToken{
|
||||
Token: newRefToken,
|
||||
UserID: token.UserID,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
)
|
||||
|
||||
type UserStore interface {
|
||||
GetUserByEmailPhone(ctx context.Context, emailPhone EmailPhone) (domain.User, error)
|
||||
GetUserByEmailPhone(ctx context.Context, email, phone string) (domain.User, error)
|
||||
}
|
||||
type TokenStore interface {
|
||||
CreateRefreshToken(ctx context.Context, rt domain.RefreshToken) error
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
package authentication
|
||||
|
||||
type EmailPhone struct {
|
||||
Email ValidString
|
||||
PhoneNumber ValidString
|
||||
Password ValidString
|
||||
}
|
||||
// type EmailPhone struct {
|
||||
// Email ValidString
|
||||
// PhoneNumber ValidString
|
||||
// Password ValidString
|
||||
// }
|
||||
type ValidString struct {
|
||||
Value string
|
||||
Valid bool
|
||||
|
|
|
|||
|
|
@ -2,17 +2,33 @@ package httpserver
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||
customvalidator "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/validator"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type JwtConfig struct {
|
||||
JwtAccessKey string
|
||||
JwtAccessExpiry int
|
||||
}
|
||||
type App struct {
|
||||
fiber *fiber.App
|
||||
port int
|
||||
fiber *fiber.App
|
||||
logger *slog.Logger
|
||||
port int
|
||||
authSvc *authentication.Service
|
||||
validator *customvalidator.CustomValidator
|
||||
JwtConfig JwtConfig
|
||||
}
|
||||
|
||||
func NewApp(port int) *App {
|
||||
func NewApp(
|
||||
port int, validator *customvalidator.CustomValidator,
|
||||
authSvc *authentication.Service,
|
||||
logger *slog.Logger,
|
||||
JwtConfig JwtConfig,
|
||||
) *App {
|
||||
app := fiber.New(fiber.Config{
|
||||
CaseSensitive: true,
|
||||
DisableHeaderNormalizing: true,
|
||||
|
|
@ -20,8 +36,12 @@ func NewApp(port int) *App {
|
|||
JSONDecoder: sonic.Unmarshal,
|
||||
})
|
||||
s := &App{
|
||||
fiber: app,
|
||||
port: port,
|
||||
fiber: app,
|
||||
port: port,
|
||||
authSvc: authSvc,
|
||||
validator: validator,
|
||||
logger: logger,
|
||||
JwtConfig: JwtConfig,
|
||||
}
|
||||
|
||||
s.initAppRoutes()
|
||||
|
|
|
|||
6
internal/web_server/dto/user.go
Normal file
6
internal/web_server/dto/user.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package dto
|
||||
|
||||
var loginCustomerRes struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package user
|
||||
package jwtutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
|
@ -20,15 +20,17 @@ var (
|
|||
type UserClaim struct {
|
||||
jwt.RegisteredClaims
|
||||
UserId string
|
||||
Role string
|
||||
}
|
||||
|
||||
func CreateJwt(userId string, key string, expiry int) (string, error) {
|
||||
func CreateJwt(userId string, Role string, key string, expiry int) (string, error) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, UserClaim{RegisteredClaims: jwt.RegisteredClaims{Issuer: "github.com/lafetz/snippitstash",
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
Audience: jwt.ClaimStrings{"fortune.com"},
|
||||
NotBefore: jwt.NewNumericDate(time.Now()),
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(expiry) * time.Second))},
|
||||
UserId: userId,
|
||||
Role: Role,
|
||||
})
|
||||
jwtToken, err := token.SignedString([]byte(key)) //
|
||||
return jwtToken, err
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package user
|
||||
package jwtutil
|
||||
|
||||
// func TestCreateJwt(t *testing.T) {
|
||||
// // Define a user to test
|
||||
|
|
|
|||
43
internal/web_server/middleware.go
Normal file
43
internal/web_server/middleware.go
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
package httpserver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func (a *App) authMiddleware(c *fiber.Ctx) error {
|
||||
|
||||
authHeader := c.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Authorization header missing")
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(authHeader, "Bearer ") {
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Invalid authorization header format")
|
||||
}
|
||||
|
||||
accessToken := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
c.Locals("access_token", accessToken)
|
||||
claim, err := jwtutil.ParseJwt(accessToken, a.JwtConfig.JwtAccessKey)
|
||||
if err != nil {
|
||||
if errors.Is(err, jwtutil.ErrExpiredToken) {
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Access token expired")
|
||||
}
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Invalid access token")
|
||||
}
|
||||
|
||||
refreshToken := c.Get("Refresh-Token")
|
||||
if refreshToken == "" {
|
||||
|
||||
// refreshToken = c.Cookies("refresh_token", "")
|
||||
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Refresh token missing")
|
||||
}
|
||||
c.Locals("user_id", claim.UserId)
|
||||
c.Locals("role", claim.Role)
|
||||
c.Locals("refresh_token", refreshToken)
|
||||
return c.Next()
|
||||
}
|
||||
47
internal/web_server/response/res.go
Normal file
47
internal/web_server/response/res.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package response
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type Status string
|
||||
|
||||
const (
|
||||
Error Status = "error"
|
||||
Success Status = "success"
|
||||
)
|
||||
|
||||
type APIResponse struct {
|
||||
Status Status `json:"status"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data,omitempty"`
|
||||
Metadata interface{} `json:"metadata,omitempty"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
func NewAPIResponse(
|
||||
status Status, message string,
|
||||
data interface{}, metadata interface{},
|
||||
) APIResponse {
|
||||
|
||||
return APIResponse{
|
||||
Status: status,
|
||||
Message: message,
|
||||
Data: data,
|
||||
Metadata: metadata,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
}
|
||||
func WriteJSON(c *fiber.Ctx, status int, message string, data, metadata interface{}) error {
|
||||
var apiStatus Status
|
||||
if status >= 200 && status <= 299 {
|
||||
apiStatus = Success
|
||||
} else {
|
||||
apiStatus = Error
|
||||
}
|
||||
apiRes := NewAPIResponse(apiStatus, message, data, metadata)
|
||||
|
||||
return c.Status(status).JSON(apiRes)
|
||||
}
|
||||
|
|
@ -1,5 +1,23 @@
|
|||
package httpserver
|
||||
|
||||
import (
|
||||
_ "github.com/SamuelTariku/FortuneBet-Backend/docs"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
fiberSwagger "github.com/swaggo/fiber-swagger"
|
||||
)
|
||||
|
||||
func (a *App) initAppRoutes() {
|
||||
// a.fiber.Group("/users", users.CreateAccount(a.userAPI))
|
||||
a.fiber.Post("/auth/login", a.LoginCustomer)
|
||||
a.fiber.Post("/auth/refresh", a.authMiddleware, a.RefreshToken)
|
||||
a.fiber.Post("/auth/logout", a.authMiddleware, a.LogOutCustomer)
|
||||
a.fiber.Get("/auth/test", a.authMiddleware, func(c *fiber.Ctx) error {
|
||||
userId := c.Locals("user_id")
|
||||
role := c.Locals("role")
|
||||
refreshToken := c.Locals("refresh_token")
|
||||
a.logger.Info("User ID: " + userId.(string))
|
||||
a.logger.Info("Role: " + role.(string))
|
||||
a.logger.Info("Refresh Token: " + refreshToken.(string))
|
||||
return c.SendString("Test endpoint")
|
||||
})
|
||||
a.fiber.Get("/swagger/*", fiberSwagger.WrapHandler)
|
||||
}
|
||||
|
|
|
|||
170
internal/web_server/user_handler.go
Normal file
170
internal/web_server/user_handler.go
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
package httpserver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/services/authentication"
|
||||
jwtutil "github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/jwt"
|
||||
"github.com/SamuelTariku/FortuneBet-Backend/internal/web_server/response"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type loginCustomerReq struct {
|
||||
Email string `json:"email" example:"john.doe@example.com"`
|
||||
PhoneNumber string `json:"phone_number" example:"1234567890"`
|
||||
Password string `json:"password" example:"password123"`
|
||||
}
|
||||
|
||||
type loginCustomerRes struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
// LoginCustomer godoc
|
||||
// @Summary Login customer
|
||||
// @Description Login customer
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param login body loginCustomerReq true "Login customer"
|
||||
// @Success 200 {object} loginCustomerRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /auth/login [post]
|
||||
func (a *App) LoginCustomer(c *fiber.Ctx) error {
|
||||
var req loginCustomerReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
a.logger.Error("Login failed", "error", err)
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
|
||||
}
|
||||
valErrs, ok := a.validator.Validate(c, req)
|
||||
if !ok {
|
||||
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
return nil
|
||||
}
|
||||
successRes, err := a.authSvc.Login(c.Context(), req.Email, req.PhoneNumber, req.Password)
|
||||
if err != nil {
|
||||
a.logger.Info("Login failed", "error", err)
|
||||
if errors.Is(err, authentication.ErrInvalidPassword) {
|
||||
response.WriteJSON(c, fiber.StatusUnauthorized, "Invalid password or not registered", nil, nil)
|
||||
return nil
|
||||
}
|
||||
if errors.Is(err, authentication.ErrUserNotFound) {
|
||||
response.WriteJSON(c, fiber.StatusUnauthorized, "Invalid password or not registered", nil, nil)
|
||||
return nil
|
||||
}
|
||||
a.logger.Error("Login failed", "error", err)
|
||||
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
|
||||
return nil
|
||||
|
||||
}
|
||||
accessToken, err := jwtutil.CreateJwt(strconv.Itoa(int(successRes.UserId)), successRes.Role, a.JwtConfig.JwtAccessKey, a.JwtConfig.JwtAccessExpiry)
|
||||
res := loginCustomerRes{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: successRes.RfToken,
|
||||
}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Login successful", res, nil)
|
||||
}
|
||||
|
||||
type refreshToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
// RefreshToken godoc
|
||||
// @Summary Refresh token
|
||||
// @Description Refresh token
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param refresh body refreshToken true "tokens"
|
||||
// @Success 200 {object} loginCustomerRes
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /auth/refresh [post]
|
||||
func (a *App) RefreshToken(c *fiber.Ctx) error {
|
||||
var req refreshToken
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
|
||||
}
|
||||
valErrs, ok := a.validator.Validate(c, req)
|
||||
if !ok {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
return nil
|
||||
}
|
||||
rf, err := a.authSvc.RefreshToken(c.Context(), req.RefreshToken)
|
||||
if err != nil {
|
||||
a.logger.Info("Refresh token failed", "error", err)
|
||||
if errors.Is(err, authentication.ErrExpiredToken) {
|
||||
response.WriteJSON(c, fiber.StatusUnauthorized, "The refresh token has expired", nil, nil)
|
||||
return nil
|
||||
}
|
||||
if errors.Is(err, authentication.ErrRefreshTokenNotFound) {
|
||||
response.WriteJSON(c, fiber.StatusUnauthorized, "Refresh token not found", nil, nil)
|
||||
return nil
|
||||
}
|
||||
a.logger.Error("Refresh token failed", "error", err)
|
||||
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
|
||||
return nil
|
||||
}
|
||||
accessToken, err := jwtutil.CreateJwt("", "", a.JwtConfig.JwtAccessKey, a.JwtConfig.JwtAccessExpiry)
|
||||
if err != nil {
|
||||
a.logger.Error("Create jwt failed", "error", err)
|
||||
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
res := loginCustomerRes{
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: rf,
|
||||
}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "refresh successful", res, nil)
|
||||
}
|
||||
|
||||
type logoutReq struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
// LogOutCustomer godoc
|
||||
// @Summary Logout customer
|
||||
// @Description Logout customer
|
||||
// @Tags auth
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param logout body logoutReq true "Logout customer"
|
||||
// @Success 200 {object} response.APIResponse
|
||||
// @Failure 400 {object} response.APIResponse
|
||||
// @Failure 401 {object} response.APIResponse
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /auth/logout [post]
|
||||
func (a *App) LogOutCustomer(c *fiber.Ctx) error {
|
||||
var req logoutReq
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", nil, nil)
|
||||
}
|
||||
valErrs, ok := a.validator.Validate(c, req)
|
||||
if !ok {
|
||||
response.WriteJSON(c, fiber.StatusBadRequest, "Invalid request", valErrs, nil)
|
||||
return nil
|
||||
}
|
||||
err := a.authSvc.Logout(c.Context(), req.RefreshToken)
|
||||
if err != nil {
|
||||
a.logger.Info("Logout failed", "error", err)
|
||||
if errors.Is(err, authentication.ErrExpiredToken) {
|
||||
response.WriteJSON(c, fiber.StatusUnauthorized, "The refresh token has expired", nil, nil)
|
||||
return nil
|
||||
}
|
||||
if errors.Is(err, authentication.ErrRefreshTokenNotFound) {
|
||||
response.WriteJSON(c, fiber.StatusUnauthorized, "Refresh token not found", nil, nil)
|
||||
return nil
|
||||
}
|
||||
a.logger.Error("Logout failed", "error", err)
|
||||
response.WriteJSON(c, fiber.StatusInternalServerError, "Internal server error", nil, nil)
|
||||
return nil
|
||||
}
|
||||
return response.WriteJSON(c, fiber.StatusOK, "Logout successful", nil, nil)
|
||||
}
|
||||
|
|
@ -1 +1,65 @@
|
|||
package validator
|
||||
package customvalidator
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type CustomValidator struct {
|
||||
validate *validator.Validate
|
||||
}
|
||||
|
||||
func NewCustomValidator(validate *validator.Validate) *CustomValidator {
|
||||
|
||||
return &CustomValidator{
|
||||
validate: validate,
|
||||
}
|
||||
}
|
||||
|
||||
func (v *CustomValidator) Validate(c *fiber.Ctx, input interface{}) (map[string]string, bool) {
|
||||
err := v.validate.Struct(input)
|
||||
if err != nil {
|
||||
if validationErrors, ok := err.(validator.ValidationErrors); ok {
|
||||
errors := ValidateModel(validationErrors)
|
||||
return errors, false
|
||||
}
|
||||
}
|
||||
return nil, true
|
||||
}
|
||||
|
||||
type ValidationErrorResponse struct {
|
||||
StatusCode int `json:"statusCode"`
|
||||
Errors interface{} `json:"errors"`
|
||||
}
|
||||
|
||||
func ValidateModel(err validator.ValidationErrors) map[string]string {
|
||||
errors := make(map[string]string)
|
||||
|
||||
for _, err := range err {
|
||||
|
||||
errors[strings.ToLower(err.Field())] = errorMsgs(err.Tag(), err.Param())
|
||||
|
||||
}
|
||||
return errors
|
||||
|
||||
}
|
||||
|
||||
func errorMsgs(tag string, value string) string {
|
||||
switch tag {
|
||||
case "required":
|
||||
return "This field is required"
|
||||
case "numeric":
|
||||
return "must be numeric " + value
|
||||
case "lte":
|
||||
return "can not be greater than " + value
|
||||
case "gte":
|
||||
return "can not be less than " + value
|
||||
case "len":
|
||||
return "length should be equal to " + value
|
||||
case "email":
|
||||
return "must be a valid email address"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
16
load_env.sh
Normal file
16
load_env.sh
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#!/bin/bash
|
||||
|
||||
dotenv() {
|
||||
if [ -f .env ]; then
|
||||
while IFS='=' read -r key value; do
|
||||
if [[ ! -z "$key" && "$key" != \#* ]]; then
|
||||
export "$key=$value"
|
||||
fi
|
||||
done < .env
|
||||
else
|
||||
echo ".env file not found."
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
dotenv
|
||||
4
makefile
4
makefile
|
|
@ -12,11 +12,11 @@ build:
|
|||
go build -ldflags="-s" -o ./bin/web ./
|
||||
.PHONY: run
|
||||
run:
|
||||
echo "Running Go application"; \
|
||||
@echo "Running Go application"; \
|
||||
go run ./cmd/main.go
|
||||
.PHONY: air
|
||||
air:
|
||||
echo "Running air"; \
|
||||
@echo "Running air"; \
|
||||
air -c .air.toml
|
||||
.PHONY: migrations/up
|
||||
migrations/new:
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user