From b2d5659aec0ced58f88958bbc7390ec33e4df68e Mon Sep 17 00:00:00 2001 From: AmirMahdi Qiasvand Date: Mon, 15 Sep 2025 16:45:16 +0330 Subject: [PATCH] feat: add http handlers, docs --- docs/docs.go | 164 +++++++++++++++++++-- docs/swagger.json | 159 ++++++++++++++++++-- docs/swagger.yaml | 107 ++++++++++++-- internal/api/dto/auth.go | 13 ++ internal/api/http/handlers/auth_handler.go | 62 ++++++++ internal/api/http/setup.go | 2 + internal/usecase/auth_service.go | 2 + 7 files changed, 466 insertions(+), 43 deletions(-) diff --git a/docs/docs.go b/docs/docs.go index a709b06..1bd9670 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -9,16 +9,7 @@ const docTemplate = `{ "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" - }, + "contact": {}, "version": "{{.Version}}" }, "host": "{{.Host}}", @@ -127,6 +118,110 @@ const docTemplate = `{ } } } + }, + "/auth/send-otp": { + "post": { + "description": "Send OTP code to the provided phone number", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Send OTP code", + "parameters": [ + { + "description": "OTP Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OTPProviderReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.OTPProviderResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/auth/verify-otp": { + "post": { + "description": "Verify the provided OTP code for the phone number", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Verify OTP code", + "parameters": [ + { + "description": "OTP Verify Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OTPVerifyRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.OTPVerifyResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } } }, "definitions": { @@ -183,18 +278,57 @@ const docTemplate = `{ "type": "string" } } + }, + "dto.OTPProviderReq": { + "type": "object", + "properties": { + "receptor": { + "type": "string" + } + } + }, + "dto.OTPProviderResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "dto.OTPVerifyRequest": { + "type": "object", + "required": [ + "code", + "phone" + ], + "properties": { + "code": { + "type": "string" + }, + "phone": { + "type": "string" + } + } + }, + "dto.OTPVerifyResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } } } }` // SwaggerInfo holds exported Swagger Info so clients can modify it var SwaggerInfo = &swag.Spec{ - Version: "1.0", - Host: "localhost:8080", - BasePath: "/api", + Version: "", + Host: "", + BasePath: "", Schemes: []string{}, - Title: "DeZone API", - Description: "This is the DeZone backend API server.", + Title: "", + Description: "", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, LeftDelim: "{{", diff --git a/docs/swagger.json b/docs/swagger.json index 62aae28..a03419f 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1,22 +1,8 @@ { "swagger": "2.0", "info": { - "description": "This is the DeZone backend API server.", - "title": "DeZone 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" + "contact": {} }, - "host": "localhost:8080", - "basePath": "/api", "paths": { "/auth/authenticate": { "post": { @@ -121,6 +107,110 @@ } } } + }, + "/auth/send-otp": { + "post": { + "description": "Send OTP code to the provided phone number", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Send OTP code", + "parameters": [ + { + "description": "OTP Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OTPProviderReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.OTPProviderResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/auth/verify-otp": { + "post": { + "description": "Verify the provided OTP code for the phone number", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Verify OTP code", + "parameters": [ + { + "description": "OTP Verify Request", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OTPVerifyRequest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.OTPVerifyResponse" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } } }, "definitions": { @@ -177,6 +267,45 @@ "type": "string" } } + }, + "dto.OTPProviderReq": { + "type": "object", + "properties": { + "receptor": { + "type": "string" + } + } + }, + "dto.OTPProviderResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + }, + "dto.OTPVerifyRequest": { + "type": "object", + "required": [ + "code", + "phone" + ], + "properties": { + "code": { + "type": "string" + }, + "phone": { + "type": "string" + } + } + }, + "dto.OTPVerifyResponse": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } } } } \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 45c3627..adc198c 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,4 +1,3 @@ -basePath: /api definitions: dto.AuthenticateRequest: properties: @@ -35,19 +34,33 @@ definitions: timeStamp: type: string type: object -host: localhost:8080 + dto.OTPProviderReq: + properties: + receptor: + type: string + type: object + dto.OTPProviderResponse: + properties: + message: + type: string + type: object + dto.OTPVerifyRequest: + properties: + code: + type: string + phone: + type: string + required: + - code + - phone + type: object + dto.OTPVerifyResponse: + properties: + message: + type: string + type: object info: - contact: - email: support@swagger.io - name: API Support - url: http://www.swagger.io/support - description: This is the DeZone backend API server. - license: - name: Apache 2.0 - url: http://www.apache.org/licenses/LICENSE-2.0.html - termsOfService: http://swagger.io/terms/ - title: DeZone API - version: "1.0" + contact: {} paths: /auth/authenticate: post: @@ -117,4 +130,72 @@ paths: summary: Generate authentication challenge tags: - auth + /auth/send-otp: + post: + consumes: + - application/json + description: Send OTP code to the provided phone number + parameters: + - description: OTP Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.OTPProviderReq' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.OTPProviderResponse' + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + "500": + description: Internal Server Error + schema: + additionalProperties: + type: string + type: object + summary: Send OTP code + tags: + - auth + /auth/verify-otp: + post: + consumes: + - application/json + description: Verify the provided OTP code for the phone number + parameters: + - description: OTP Verify Request + in: body + name: request + required: true + schema: + $ref: '#/definitions/dto.OTPVerifyRequest' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/dto.OTPVerifyResponse' + "400": + description: Bad Request + schema: + additionalProperties: + type: string + type: object + "401": + description: Unauthorized + schema: + additionalProperties: + type: string + type: object + summary: Verify OTP code + tags: + - auth swagger: "2.0" diff --git a/internal/api/dto/auth.go b/internal/api/dto/auth.go index 145cb6a..2a41ee9 100644 --- a/internal/api/dto/auth.go +++ b/internal/api/dto/auth.go @@ -28,3 +28,16 @@ type RefreshTokenRequest struct { type OTPProviderReq struct { Receptor string `json:"receptor"` } + +type OTPProviderResponse struct { + Message string `json:"message"` +} + +type OTPVerifyRequest struct { + Phone string `json:"phone" validate:"required"` + Code string `json:"code" validate:"required"` +} + +type OTPVerifyResponse struct { + Message string `json:"message"` +} diff --git a/internal/api/http/handlers/auth_handler.go b/internal/api/http/handlers/auth_handler.go index 4fb2d2d..432e7c0 100644 --- a/internal/api/http/handlers/auth_handler.go +++ b/internal/api/http/handlers/auth_handler.go @@ -91,6 +91,68 @@ func (h *AuthHandler) Authenticate(c *fiber.Ctx) error { }) } +// SendOTP sends OTP code to the provided phone number +// @Summary Send OTP code +// @Description Send OTP code to the provided phone number +// @Tags auth +// @Accept json +// @Produce json +// @Param request body dto.OTPProviderReq true "OTP Request" +// @Success 200 {object} dto.OTPProviderResponse +// @Failure 400 {object} map[string]string +// @Failure 500 {object} map[string]string +// @Router /auth/send-otp [post] +func (h *AuthHandler) SendOTP(c *fiber.Ctx) error { + var req dto.OTPProviderReq + if err := c.BodyParser(&req); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "invalid request body", + }) + } + + _, err := h.authService.SendOTPCode(c.Context(), req.Receptor) + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": err.Error(), + }) + } + + return c.Status(fiber.StatusOK).JSON(dto.OTPProviderResponse{ + Message: "OTP code sent successfully", + }) +} + +// VerifyOTP verifies the OTP code +// @Summary Verify OTP code +// @Description Verify the provided OTP code for the phone number +// @Tags auth +// @Accept json +// @Produce json +// @Param request body dto.OTPVerifyRequest true "OTP Verify Request" +// @Success 200 {object} dto.OTPVerifyResponse +// @Failure 400 {object} map[string]string +// @Failure 401 {object} map[string]string +// @Router /auth/verify-otp [post] +func (h *AuthHandler) VerifyOTP(c *fiber.Ctx) error { + var req dto.OTPVerifyRequest + if err := c.BodyParser(&req); err != nil { + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "error": "invalid request body", + }) + } + + err := h.authService.VerifyOTP(c.Context(), req.Phone, req.Code) + if err != nil { + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "error": err.Error(), + }) + } + + return c.Status(fiber.StatusOK).JSON(dto.OTPVerifyResponse{ + Message: "OTP verified successfully", + }) +} + func (h *AuthHandler) HelloWorld(c *fiber.Ctx) error { return c.Status(fiber.StatusOK).JSON(fiber.Map{ "message": "Hello, World!", diff --git a/internal/api/http/setup.go b/internal/api/http/setup.go index 2353428..6a72028 100644 --- a/internal/api/http/setup.go +++ b/internal/api/http/setup.go @@ -47,4 +47,6 @@ func registerPublicRoutes(router fiber.Router, app *app.AppContainer) { // Register auth routes authgroup.Post("/challenge", authHandler.GenerateChallenge) authgroup.Post("/authenticate", authHandler.Authenticate) + authgroup.Post("/send-otp", authHandler.SendOTP) + authgroup.Post("/verify-otp", authHandler.VerifyOTP) } diff --git a/internal/usecase/auth_service.go b/internal/usecase/auth_service.go index 3d1fb7f..7daab5b 100644 --- a/internal/usecase/auth_service.go +++ b/internal/usecase/auth_service.go @@ -21,6 +21,8 @@ import ( type AuthService interface { GenerateChallenge(ctx context.Context, pubKey string) (*domain.Challenge, error) Authenticate(ctx context.Context, pubKey string, signature string, chainID uint, ipAddress, userAgent string) (*UserToken, error) + SendOTPCode(ctx context.Context, phone string) (string, error) + VerifyOTP(ctx context.Context, phone, code string) error } type authService struct {