Update template go
This commit is contained in:
141
AUTHENTICATION.md
Normal file
141
AUTHENTICATION.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# JWT Authentication API Documentation
|
||||
|
||||
This document describes how to use the JWT authentication system implemented in this API service.
|
||||
|
||||
## Overview
|
||||
|
||||
The API provides JWT-based authentication with the following features:
|
||||
- User login with username/password
|
||||
- JWT token generation
|
||||
- Token validation middleware
|
||||
- Protected routes
|
||||
- User registration
|
||||
|
||||
## Endpoints
|
||||
|
||||
### Public Endpoints (No Authentication Required)
|
||||
|
||||
#### POST /api/v1/auth/login
|
||||
Login with username and password to receive a JWT token.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "password"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"token_type": "Bearer",
|
||||
"expires_in": 3600
|
||||
}
|
||||
```
|
||||
|
||||
#### POST /api/v1/auth/register
|
||||
Register a new user account.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"username": "newuser",
|
||||
"email": "user@example.com",
|
||||
"password": "securepassword",
|
||||
"role": "user"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"message": "user registered successfully"
|
||||
}
|
||||
```
|
||||
|
||||
### Protected Endpoints (Authentication Required)
|
||||
|
||||
All protected endpoints require a valid JWT token in the Authorization header:
|
||||
```
|
||||
Authorization: Bearer <your-jwt-token>
|
||||
```
|
||||
|
||||
#### GET /api/v1/auth/me
|
||||
Get information about the currently authenticated user.
|
||||
|
||||
**Headers:**
|
||||
```
|
||||
Authorization: Bearer <your-jwt-token>
|
||||
```
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"id": "1",
|
||||
"username": "admin",
|
||||
"email": "admin@example.com",
|
||||
"role": "admin"
|
||||
}
|
||||
```
|
||||
|
||||
## Demo Users
|
||||
|
||||
The system comes with pre-configured demo users:
|
||||
|
||||
| Username | Password | Role |
|
||||
|----------|----------|-------|
|
||||
| admin | password | admin |
|
||||
| user | password | user |
|
||||
|
||||
## Using the JWT Token
|
||||
|
||||
Once you receive a JWT token from the login endpoint, include it in the Authorization header for all protected endpoints:
|
||||
|
||||
```bash
|
||||
curl -X GET http://localhost:8080/api/v1/products \
|
||||
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
||||
```
|
||||
|
||||
## Token Expiration
|
||||
|
||||
JWT tokens expire after 1 hour. When a token expires, you'll need to login again to get a new token.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
To configure JWT authentication, you can set these environment variables:
|
||||
|
||||
```bash
|
||||
# JWT Secret (change this in production)
|
||||
JWT_SECRET=your-secret-key-change-this-in-production
|
||||
```
|
||||
|
||||
## Testing the Authentication
|
||||
|
||||
### 1. Login with demo user
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/api/v1/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"admin","password":"password"}'
|
||||
```
|
||||
|
||||
### 2. Use the token to access protected endpoints
|
||||
```bash
|
||||
# Get user info
|
||||
curl -X GET http://localhost:8080/api/v1/auth/me \
|
||||
-H "Authorization: Bearer <your-token>"
|
||||
|
||||
# Get products
|
||||
curl -X GET http://localhost:8080/api/v1/products \
|
||||
-H "Authorization: Bearer <your-token>"
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
The API returns appropriate HTTP status codes:
|
||||
- `200 OK` - Successful request
|
||||
- `201 Created` - Resource created successfully
|
||||
- `400 Bad Request` - Invalid request data
|
||||
- `401 Unauthorized` - Invalid or missing token
|
||||
- `500 Internal Server Error` - Server error
|
||||
@@ -48,6 +48,196 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/auth/login": {
|
||||
"post": {
|
||||
"description": "Authenticate user with username and password to receive JWT token",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Authentication"
|
||||
],
|
||||
"summary": "Login user and get JWT token",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Login credentials",
|
||||
"name": "login",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.LoginRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TokenResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/auth/me": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"description": "Get information about the currently authenticated user",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Authentication"
|
||||
],
|
||||
"summary": "Get current user info",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.User"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/auth/refresh": {
|
||||
"post": {
|
||||
"description": "Refresh the JWT token using a valid refresh token",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Authentication"
|
||||
],
|
||||
"summary": "Refresh JWT token",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Refresh token",
|
||||
"name": "refresh",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TokenResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/auth/register": {
|
||||
"post": {
|
||||
"description": "Register a new user account",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Authentication"
|
||||
],
|
||||
"summary": "Register new user",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Registration data",
|
||||
"name": "register",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/example": {
|
||||
"get": {
|
||||
"description": "Returns a simple message for GET request",
|
||||
@@ -109,6 +299,290 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/products": {
|
||||
"get": {
|
||||
"description": "Returns a list of products",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Get product",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product GET response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductGetResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Creates a new product",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Create product",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Product creation request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductCreateRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Product created successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductCreateResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/products/{id}": {
|
||||
"get": {
|
||||
"description": "Returns a single product by ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Get product by ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Product ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product GET by ID response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductGetByIDResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Product not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"description": "Updates an existing product",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Update product",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Product ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Product update request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductUpdateRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product updated successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductUpdateResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Product not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "Deletes a product by ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Delete product",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Product ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product deleted successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductDeleteResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Product not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/token/generate": {
|
||||
"post": {
|
||||
"description": "Generate a JWT token for a user",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Token"
|
||||
],
|
||||
"summary": "Generate JWT token",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User credentials",
|
||||
"name": "token",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.LoginRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TokenResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/token/generate-direct": {
|
||||
"post": {
|
||||
"description": "Generate a JWT token directly without password verification (for testing)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Token"
|
||||
],
|
||||
"summary": "Generate token directly",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User info",
|
||||
"name": "user",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TokenResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/health": {
|
||||
"get": {
|
||||
"description": "Returns the health status of the API service",
|
||||
@@ -215,6 +689,129 @@ const docTemplate = `{
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.LoginRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"password",
|
||||
"username"
|
||||
],
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductCreateRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductCreateResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductDeleteResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductGetByIDResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductGetResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductUpdateRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductUpdateResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TokenResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"access_token": {
|
||||
"type": "string"
|
||||
},
|
||||
"expires_in": {
|
||||
"type": "integer"
|
||||
},
|
||||
"token_type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.User": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
@@ -45,6 +45,196 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/auth/login": {
|
||||
"post": {
|
||||
"description": "Authenticate user with username and password to receive JWT token",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Authentication"
|
||||
],
|
||||
"summary": "Login user and get JWT token",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Login credentials",
|
||||
"name": "login",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.LoginRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TokenResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/auth/me": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"Bearer": []
|
||||
}
|
||||
],
|
||||
"description": "Get information about the currently authenticated user",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Authentication"
|
||||
],
|
||||
"summary": "Get current user info",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.User"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/auth/refresh": {
|
||||
"post": {
|
||||
"description": "Refresh the JWT token using a valid refresh token",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Authentication"
|
||||
],
|
||||
"summary": "Refresh JWT token",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Refresh token",
|
||||
"name": "refresh",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TokenResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/auth/register": {
|
||||
"post": {
|
||||
"description": "Register a new user account",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Authentication"
|
||||
],
|
||||
"summary": "Register new user",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Registration data",
|
||||
"name": "register",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/example": {
|
||||
"get": {
|
||||
"description": "Returns a simple message for GET request",
|
||||
@@ -106,6 +296,290 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/products": {
|
||||
"get": {
|
||||
"description": "Returns a list of products",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Get product",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product GET response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductGetResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Creates a new product",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Create product",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Product creation request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductCreateRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Product created successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductCreateResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/products/{id}": {
|
||||
"get": {
|
||||
"description": "Returns a single product by ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Get product by ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Product ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product GET by ID response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductGetByIDResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Product not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"description": "Updates an existing product",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Update product",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Product ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Product update request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductUpdateRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product updated successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductUpdateResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Product not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "Deletes a product by ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Delete product",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Product ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product deleted successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductDeleteResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Product not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/token/generate": {
|
||||
"post": {
|
||||
"description": "Generate a JWT token for a user",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Token"
|
||||
],
|
||||
"summary": "Generate JWT token",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User credentials",
|
||||
"name": "token",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.LoginRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TokenResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/token/generate-direct": {
|
||||
"post": {
|
||||
"description": "Generate a JWT token directly without password verification (for testing)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Token"
|
||||
],
|
||||
"summary": "Generate token directly",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User info",
|
||||
"name": "user",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TokenResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/health": {
|
||||
"get": {
|
||||
"description": "Returns the health status of the API service",
|
||||
@@ -212,6 +686,129 @@
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.LoginRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"password",
|
||||
"username"
|
||||
],
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductCreateRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductCreateResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductDeleteResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductGetByIDResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductGetResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductUpdateRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductUpdateResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TokenResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"access_token": {
|
||||
"type": "string"
|
||||
},
|
||||
"expires_in": {
|
||||
"type": "integer"
|
||||
},
|
||||
"token_type": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.User": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,86 @@ definitions:
|
||||
version:
|
||||
type: string
|
||||
type: object
|
||||
models.LoginRequest:
|
||||
properties:
|
||||
password:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
required:
|
||||
- password
|
||||
- username
|
||||
type: object
|
||||
models.ProductCreateRequest:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
models.ProductCreateResponse:
|
||||
properties:
|
||||
data: {}
|
||||
id:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
models.ProductDeleteResponse:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
models.ProductGetByIDResponse:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
models.ProductGetResponse:
|
||||
properties:
|
||||
data: {}
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
models.ProductUpdateRequest:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
models.ProductUpdateResponse:
|
||||
properties:
|
||||
data: {}
|
||||
id:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
models.TokenResponse:
|
||||
properties:
|
||||
access_token:
|
||||
type: string
|
||||
expires_in:
|
||||
type: integer
|
||||
token_type:
|
||||
type: string
|
||||
type: object
|
||||
models.User:
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
id:
|
||||
type: string
|
||||
role:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
type: object
|
||||
host: localhost:8080
|
||||
info:
|
||||
contact:
|
||||
@@ -78,6 +158,129 @@ paths:
|
||||
summary: Hello World endpoint
|
||||
tags:
|
||||
- root
|
||||
/api/v1/auth/login:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Authenticate user with username and password to receive JWT token
|
||||
parameters:
|
||||
- description: Login credentials
|
||||
in: body
|
||||
name: login
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.LoginRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.TokenResponse'
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Login user and get JWT token
|
||||
tags:
|
||||
- Authentication
|
||||
/api/v1/auth/me:
|
||||
get:
|
||||
description: Get information about the currently authenticated user
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.User'
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
security:
|
||||
- Bearer: []
|
||||
summary: Get current user info
|
||||
tags:
|
||||
- Authentication
|
||||
/api/v1/auth/refresh:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Refresh the JWT token using a valid refresh token
|
||||
parameters:
|
||||
- description: Refresh token
|
||||
in: body
|
||||
name: refresh
|
||||
required: true
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.TokenResponse'
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Refresh JWT token
|
||||
tags:
|
||||
- Authentication
|
||||
/api/v1/auth/register:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Register a new user account
|
||||
parameters:
|
||||
- description: Registration data
|
||||
in: body
|
||||
name: register
|
||||
required: true
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Register new user
|
||||
tags:
|
||||
- Authentication
|
||||
/api/v1/example:
|
||||
get:
|
||||
consumes:
|
||||
@@ -118,6 +321,194 @@ paths:
|
||||
summary: Example POST service
|
||||
tags:
|
||||
- example
|
||||
/api/v1/products:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns a list of products
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Product GET response
|
||||
schema:
|
||||
$ref: '#/definitions/models.ProductGetResponse'
|
||||
summary: Get product
|
||||
tags:
|
||||
- product
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Creates a new product
|
||||
parameters:
|
||||
- description: Product creation request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.ProductCreateRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Product created successfully
|
||||
schema:
|
||||
$ref: '#/definitions/models.ProductCreateResponse'
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
summary: Create product
|
||||
tags:
|
||||
- product
|
||||
/api/v1/products/{id}:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Deletes a product by ID
|
||||
parameters:
|
||||
- description: Product ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Product deleted successfully
|
||||
schema:
|
||||
$ref: '#/definitions/models.ProductDeleteResponse'
|
||||
"404":
|
||||
description: Product not found
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
summary: Delete product
|
||||
tags:
|
||||
- product
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns a single product by ID
|
||||
parameters:
|
||||
- description: Product ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Product GET by ID response
|
||||
schema:
|
||||
$ref: '#/definitions/models.ProductGetByIDResponse'
|
||||
"404":
|
||||
description: Product not found
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
summary: Get product by ID
|
||||
tags:
|
||||
- product
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Updates an existing product
|
||||
parameters:
|
||||
- description: Product ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: Product update request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.ProductUpdateRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Product updated successfully
|
||||
schema:
|
||||
$ref: '#/definitions/models.ProductUpdateResponse'
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"404":
|
||||
description: Product not found
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
summary: Update product
|
||||
tags:
|
||||
- product
|
||||
/api/v1/token/generate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Generate a JWT token for a user
|
||||
parameters:
|
||||
- description: User credentials
|
||||
in: body
|
||||
name: token
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.LoginRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.TokenResponse'
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
"401":
|
||||
description: Unauthorized
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Generate JWT token
|
||||
tags:
|
||||
- Token
|
||||
/api/v1/token/generate-direct:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Generate a JWT token directly without password verification (for
|
||||
testing)
|
||||
parameters:
|
||||
- description: User info
|
||||
in: body
|
||||
name: user
|
||||
required: true
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.TokenResponse'
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
summary: Generate token directly
|
||||
tags:
|
||||
- Token
|
||||
/health:
|
||||
get:
|
||||
consumes:
|
||||
|
||||
498
docs/docs.go
Normal file
498
docs/docs.go
Normal file
@@ -0,0 +1,498 @@
|
||||
// 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}}",
|
||||
"/": {
|
||||
"get": {
|
||||
"description": "Returns a hello world message",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"root"
|
||||
],
|
||||
"summary": "Hello World endpoint",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Hello world message",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.HelloWorldResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/example": {
|
||||
"get": {
|
||||
"description": "Returns a simple message for GET request",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"example"
|
||||
],
|
||||
"summary": "Example GET service",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Example GET response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ExampleGetResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Accepts a JSON payload and returns a response with an ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"example"
|
||||
],
|
||||
"summary": "Example POST service",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Example POST request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ExamplePostRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Example POST response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ExamplePostResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/products": {
|
||||
"get": {
|
||||
"description": "Returns a list of products",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Get product",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product GET response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductGetResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Creates a new product",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Create product",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Product creation request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductCreateRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Product created successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductCreateResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/products/{id}": {
|
||||
"get": {
|
||||
"description": "Returns a single product by ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Get product by ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Product ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product GET by ID response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductGetByIDResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Product not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"description": "Updates an existing product",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Update product",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Product ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Product update request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductUpdateRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product updated successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductUpdateResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Product not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "Deletes a product by ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Delete product",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Product ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product deleted successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductDeleteResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Product not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/health": {
|
||||
"get": {
|
||||
"description": "Returns the health status of the API service",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"health"
|
||||
],
|
||||
"summary": "Health check endpoint",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Health status",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.HealthResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"models.ErrorResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ExampleGetResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ExamplePostRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"age",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"age": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ExamplePostResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.HealthResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"details": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.HelloWorldResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductCreateRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductCreateResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductDeleteResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductGetByIDResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductGetResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductUpdateRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductUpdateResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = &swag.Spec{
|
||||
Version: "1.0.0",
|
||||
Host: "localhost:8080",
|
||||
BasePath: "/api/v1",
|
||||
Schemes: []string{"http", "https"},
|
||||
Title: "API Service",
|
||||
Description: "A comprehensive Go API service with Swagger documentation",
|
||||
InfoInstanceName: "swagger",
|
||||
SwaggerTemplate: docTemplate,
|
||||
}
|
||||
|
||||
func init() {
|
||||
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||
}
|
||||
480
docs/swagger.json
Normal file
480
docs/swagger.json
Normal file
@@ -0,0 +1,480 @@
|
||||
{
|
||||
"schemes": [
|
||||
"http",
|
||||
"https"
|
||||
],
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "A comprehensive Go API service with Swagger documentation",
|
||||
"title": "API Service",
|
||||
"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.0"
|
||||
},
|
||||
"host": "localhost:8080",
|
||||
"basePath": "/api/v1",
|
||||
"paths": {
|
||||
"/": {
|
||||
"get": {
|
||||
"description": "Returns a hello world message",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"root"
|
||||
],
|
||||
"summary": "Hello World endpoint",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Hello world message",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.HelloWorldResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/example": {
|
||||
"get": {
|
||||
"description": "Returns a simple message for GET request",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"example"
|
||||
],
|
||||
"summary": "Example GET service",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Example GET response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ExampleGetResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Accepts a JSON payload and returns a response with an ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"example"
|
||||
],
|
||||
"summary": "Example POST service",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Example POST request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ExamplePostRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Example POST response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ExamplePostResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/products": {
|
||||
"get": {
|
||||
"description": "Returns a list of products",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Get product",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product GET response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductGetResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Creates a new product",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Create product",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Product creation request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductCreateRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Product created successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductCreateResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/products/{id}": {
|
||||
"get": {
|
||||
"description": "Returns a single product by ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Get product by ID",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Product ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product GET by ID response",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductGetByIDResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Product not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"description": "Updates an existing product",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Update product",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Product ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "Product update request",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductUpdateRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product updated successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductUpdateResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Product not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "Deletes a product by ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"product"
|
||||
],
|
||||
"summary": "Delete product",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Product ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Product deleted successfully",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ProductDeleteResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Product not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/health": {
|
||||
"get": {
|
||||
"description": "Returns the health status of the API service",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"health"
|
||||
],
|
||||
"summary": "Health check endpoint",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Health status",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.HealthResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"models.ErrorResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"type": "integer"
|
||||
},
|
||||
"error": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ExampleGetResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ExamplePostRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"age",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"age": {
|
||||
"type": "integer"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ExamplePostResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.HealthResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"details": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"status": {
|
||||
"type": "string"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.HelloWorldResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductCreateRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductCreateResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductDeleteResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductGetByIDResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductGetResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductUpdateRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ProductUpdateResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
316
docs/swagger.yaml
Normal file
316
docs/swagger.yaml
Normal file
@@ -0,0 +1,316 @@
|
||||
basePath: /api/v1
|
||||
definitions:
|
||||
models.ErrorResponse:
|
||||
properties:
|
||||
code:
|
||||
type: integer
|
||||
error:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
models.ExampleGetResponse:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
models.ExamplePostRequest:
|
||||
properties:
|
||||
age:
|
||||
type: integer
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- age
|
||||
- name
|
||||
type: object
|
||||
models.ExamplePostResponse:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
models.HealthResponse:
|
||||
properties:
|
||||
details:
|
||||
additionalProperties:
|
||||
type: string
|
||||
type: object
|
||||
status:
|
||||
type: string
|
||||
timestamp:
|
||||
type: string
|
||||
type: object
|
||||
models.HelloWorldResponse:
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
version:
|
||||
type: string
|
||||
type: object
|
||||
models.ProductCreateRequest:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
models.ProductCreateResponse:
|
||||
properties:
|
||||
data: {}
|
||||
id:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
models.ProductDeleteResponse:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
models.ProductGetByIDResponse:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
models.ProductGetResponse:
|
||||
properties:
|
||||
data: {}
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
models.ProductUpdateRequest:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
models.ProductUpdateResponse:
|
||||
properties:
|
||||
data: {}
|
||||
id:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
host: localhost:8080
|
||||
info:
|
||||
contact:
|
||||
email: support@swagger.io
|
||||
name: API Support
|
||||
url: http://www.swagger.io/support
|
||||
description: A comprehensive Go API service with Swagger documentation
|
||||
license:
|
||||
name: Apache 2.0
|
||||
url: http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
termsOfService: http://swagger.io/terms/
|
||||
title: API Service
|
||||
version: 1.0.0
|
||||
paths:
|
||||
/:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns a hello world message
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Hello world message
|
||||
schema:
|
||||
$ref: '#/definitions/models.HelloWorldResponse'
|
||||
summary: Hello World endpoint
|
||||
tags:
|
||||
- root
|
||||
/api/v1/example:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns a simple message for GET request
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Example GET response
|
||||
schema:
|
||||
$ref: '#/definitions/models.ExampleGetResponse'
|
||||
summary: Example GET service
|
||||
tags:
|
||||
- example
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Accepts a JSON payload and returns a response with an ID
|
||||
parameters:
|
||||
- description: Example POST request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.ExamplePostRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Example POST response
|
||||
schema:
|
||||
$ref: '#/definitions/models.ExamplePostResponse'
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
summary: Example POST service
|
||||
tags:
|
||||
- example
|
||||
/api/v1/products:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns a list of products
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Product GET response
|
||||
schema:
|
||||
$ref: '#/definitions/models.ProductGetResponse'
|
||||
summary: Get product
|
||||
tags:
|
||||
- product
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Creates a new product
|
||||
parameters:
|
||||
- description: Product creation request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.ProductCreateRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Product created successfully
|
||||
schema:
|
||||
$ref: '#/definitions/models.ProductCreateResponse'
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
summary: Create product
|
||||
tags:
|
||||
- product
|
||||
/api/v1/products/{id}:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Deletes a product by ID
|
||||
parameters:
|
||||
- description: Product ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Product deleted successfully
|
||||
schema:
|
||||
$ref: '#/definitions/models.ProductDeleteResponse'
|
||||
"404":
|
||||
description: Product not found
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
summary: Delete product
|
||||
tags:
|
||||
- product
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns a single product by ID
|
||||
parameters:
|
||||
- description: Product ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Product GET by ID response
|
||||
schema:
|
||||
$ref: '#/definitions/models.ProductGetByIDResponse'
|
||||
"404":
|
||||
description: Product not found
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
summary: Get product by ID
|
||||
tags:
|
||||
- product
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Updates an existing product
|
||||
parameters:
|
||||
- description: Product ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
- description: Product update request
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.ProductUpdateRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Product updated successfully
|
||||
schema:
|
||||
$ref: '#/definitions/models.ProductUpdateResponse'
|
||||
"400":
|
||||
description: Bad request
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
"404":
|
||||
description: Product not found
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
summary: Update product
|
||||
tags:
|
||||
- product
|
||||
/health:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns the health status of the API service
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: Health status
|
||||
schema:
|
||||
$ref: '#/definitions/models.HealthResponse'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/models.ErrorResponse'
|
||||
summary: Health check endpoint
|
||||
tags:
|
||||
- health
|
||||
schemes:
|
||||
- http
|
||||
- https
|
||||
swagger: "2.0"
|
||||
132
internal/handlers/auth.go
Normal file
132
internal/handlers/auth.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"api-service/internal/models"
|
||||
"api-service/internal/services"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// AuthHandler handles authentication endpoints
|
||||
type AuthHandler struct {
|
||||
authService *services.AuthService
|
||||
}
|
||||
|
||||
// NewAuthHandler creates a new authentication handler
|
||||
func NewAuthHandler(authService *services.AuthService) *AuthHandler {
|
||||
return &AuthHandler{
|
||||
authService: authService,
|
||||
}
|
||||
}
|
||||
|
||||
// Login godoc
|
||||
// @Summary Login user and get JWT token
|
||||
// @Description Authenticate user with username and password to receive JWT token
|
||||
// @Tags Authentication
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param login body models.LoginRequest true "Login credentials"
|
||||
// @Success 200 {object} models.TokenResponse
|
||||
// @Failure 400 {object} map[string]string "Bad request"
|
||||
// @Failure 401 {object} map[string]string "Unauthorized"
|
||||
// @Router /api/v1/auth/login [post]
|
||||
func (h *AuthHandler) Login(c *gin.Context) {
|
||||
var loginReq models.LoginRequest
|
||||
|
||||
// Bind JSON request
|
||||
if err := c.ShouldBindJSON(&loginReq); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Authenticate user
|
||||
tokenResponse, err := h.authService.Login(loginReq.Username, loginReq.Password)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, tokenResponse)
|
||||
}
|
||||
|
||||
// RefreshToken godoc
|
||||
// @Summary Refresh JWT token
|
||||
// @Description Refresh the JWT token using a valid refresh token
|
||||
// @Tags Authentication
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param refresh body map[string]string true "Refresh token"
|
||||
// @Success 200 {object} models.TokenResponse
|
||||
// @Failure 400 {object} map[string]string "Bad request"
|
||||
// @Failure 401 {object} map[string]string "Unauthorized"
|
||||
// @Router /api/v1/auth/refresh [post]
|
||||
func (h *AuthHandler) RefreshToken(c *gin.Context) {
|
||||
// For now, this is a placeholder for refresh token functionality
|
||||
// In a real implementation, you would handle refresh tokens here
|
||||
c.JSON(http.StatusNotImplemented, gin.H{"error": "refresh token not implemented"})
|
||||
}
|
||||
|
||||
// Register godoc
|
||||
// @Summary Register new user
|
||||
// @Description Register a new user account
|
||||
// @Tags Authentication
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param register body map[string]string true "Registration data"
|
||||
// @Success 201 {object} map[string]string
|
||||
// @Failure 400 {object} map[string]string "Bad request"
|
||||
// @Router /api/v1/auth/register [post]
|
||||
func (h *AuthHandler) Register(c *gin.Context) {
|
||||
var registerReq struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Password string `json:"password" binding:"required,min=6"`
|
||||
Role string `json:"role" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(®isterReq); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
err := h.authService.RegisterUser(
|
||||
registerReq.Username,
|
||||
registerReq.Email,
|
||||
registerReq.Password,
|
||||
registerReq.Role,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"message": "user registered successfully"})
|
||||
}
|
||||
|
||||
// Me godoc
|
||||
// @Summary Get current user info
|
||||
// @Description Get information about the currently authenticated user
|
||||
// @Tags Authentication
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} models.User
|
||||
// @Failure 401 {object} map[string]string "Unauthorized"
|
||||
// @Router /api/v1/auth/me [get]
|
||||
func (h *AuthHandler) Me(c *gin.Context) {
|
||||
// Get user info from context (set by middleware)
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "user not authenticated"})
|
||||
return
|
||||
}
|
||||
|
||||
// In a real implementation, you would fetch user details from database
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"id": userID,
|
||||
"username": c.GetString("username"),
|
||||
"email": c.GetString("email"),
|
||||
"role": c.GetString("role"),
|
||||
})
|
||||
}
|
||||
124
internal/handlers/product.go
Normal file
124
internal/handlers/product.go
Normal file
@@ -0,0 +1,124 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"api-service/internal/models"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ProductHandler handles product services
|
||||
type ProductHandler struct{}
|
||||
|
||||
// NewProductHandler creates a new ProductHandler
|
||||
func NewProductHandler() *ProductHandler {
|
||||
return &ProductHandler{}
|
||||
}
|
||||
|
||||
// GetProduct godoc
|
||||
// @Summary Get product
|
||||
// @Description Returns a list of products
|
||||
// @Tags product
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} models.ProductGetResponse "Product GET response"
|
||||
// @Router /api/v1/products [get]
|
||||
func (h *ProductHandler) GetProduct(c *gin.Context) {
|
||||
response := models.ProductGetResponse{
|
||||
Message: "List of products",
|
||||
Data: []string{"Product 1", "Product 2"},
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// GetProductByID godoc
|
||||
// @Summary Get product by ID
|
||||
// @Description Returns a single product by ID
|
||||
// @Tags product
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Product ID"
|
||||
// @Success 200 {object} models.ProductGetByIDResponse "Product GET by ID response"
|
||||
// @Failure 404 {object} models.ErrorResponse "Product not found"
|
||||
// @Router /api/v1/products/{id} [get]
|
||||
func (h *ProductHandler) GetProductByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
response := models.ProductGetByIDResponse{
|
||||
ID: id,
|
||||
Message: "Product details",
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// CreateProduct godoc
|
||||
// @Summary Create product
|
||||
// @Description Creates a new product
|
||||
// @Tags product
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body models.ProductCreateRequest true "Product creation request"
|
||||
// @Success 201 {object} models.ProductCreateResponse "Product created successfully"
|
||||
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
||||
// @Router /api/v1/products [post]
|
||||
func (h *ProductHandler) CreateProduct(c *gin.Context) {
|
||||
var req models.ProductCreateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
response := models.ProductCreateResponse{
|
||||
ID: uuid.NewString(),
|
||||
Message: "Product created successfully",
|
||||
Data: req,
|
||||
}
|
||||
c.JSON(http.StatusCreated, response)
|
||||
}
|
||||
|
||||
// UpdateProduct godoc
|
||||
// @Summary Update product
|
||||
// @Description Updates an existing product
|
||||
// @Tags product
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Product ID"
|
||||
// @Param request body models.ProductUpdateRequest true "Product update request"
|
||||
// @Success 200 {object} models.ProductUpdateResponse "Product updated successfully"
|
||||
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
||||
// @Failure 404 {object} models.ErrorResponse "Product not found"
|
||||
// @Router /api/v1/products/{id} [put]
|
||||
func (h *ProductHandler) UpdateProduct(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req models.ProductUpdateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
response := models.ProductUpdateResponse{
|
||||
ID: id,
|
||||
Message: "Product updated successfully",
|
||||
Data: req,
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// DeleteProduct godoc
|
||||
// @Summary Delete product
|
||||
// @Description Deletes a product by ID
|
||||
// @Tags product
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Product ID"
|
||||
// @Success 200 {object} models.ProductDeleteResponse "Product deleted successfully"
|
||||
// @Failure 404 {object} models.ErrorResponse "Product not found"
|
||||
// @Router /api/v1/products/{id} [delete]
|
||||
func (h *ProductHandler) DeleteProduct(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
response := models.ProductDeleteResponse{
|
||||
ID: id,
|
||||
Message: "Product deleted successfully",
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
95
internal/handlers/token_handler.go
Normal file
95
internal/handlers/token_handler.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"api-service/internal/models"
|
||||
"api-service/internal/services"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// TokenHandler handles token generation endpoints
|
||||
type TokenHandler struct {
|
||||
authService *services.AuthService
|
||||
}
|
||||
|
||||
// NewTokenHandler creates a new token handler
|
||||
func NewTokenHandler(authService *services.AuthService) *TokenHandler {
|
||||
return &TokenHandler{
|
||||
authService: authService,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateToken godoc
|
||||
// @Summary Generate JWT token
|
||||
// @Description Generate a JWT token for a user
|
||||
// @Tags Token
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param token body models.LoginRequest true "User credentials"
|
||||
// @Success 200 {object} models.TokenResponse
|
||||
// @Failure 400 {object} map[string]string "Bad request"
|
||||
// @Failure 401 {object} map[string]string "Unauthorized"
|
||||
// @Router /api/v1/token/generate [post]
|
||||
func (h *TokenHandler) GenerateToken(c *gin.Context) {
|
||||
var loginReq models.LoginRequest
|
||||
|
||||
// Bind JSON request
|
||||
if err := c.ShouldBindJSON(&loginReq); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Generate token
|
||||
tokenResponse, err := h.authService.Login(loginReq.Username, loginReq.Password)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, tokenResponse)
|
||||
}
|
||||
|
||||
// GenerateTokenDirect godoc
|
||||
// @Summary Generate token directly
|
||||
// @Description Generate a JWT token directly without password verification (for testing)
|
||||
// @Tags Token
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param user body map[string]string true "User info"
|
||||
// @Success 200 {object} models.TokenResponse
|
||||
// @Failure 400 {object} map[string]string "Bad request"
|
||||
// @Router /api/v1/token/generate-direct [post]
|
||||
func (h *TokenHandler) GenerateTokenDirect(c *gin.Context) {
|
||||
var req struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Email string `json:"email" binding:"required"`
|
||||
Role string `json:"role" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Create a temporary user for token generation
|
||||
user := &models.User{
|
||||
ID: "temp-" + req.Username,
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
Role: req.Role,
|
||||
}
|
||||
|
||||
// Generate token directly
|
||||
token, err := h.authService.GenerateTokenForUser(user)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, models.TokenResponse{
|
||||
AccessToken: token,
|
||||
TokenType: "Bearer",
|
||||
ExpiresIn: 3600,
|
||||
})
|
||||
}
|
||||
@@ -197,30 +197,30 @@ func AuthMiddleware() gin.HandlerFunc {
|
||||
// "github.com/gin-gonic/gin"
|
||||
// )
|
||||
|
||||
// // AuthMiddleware validates Bearer token in Authorization header
|
||||
// func AuthMiddleware() gin.HandlerFunc {
|
||||
// return func(c *gin.Context) {
|
||||
// authHeader := c.GetHeader("Authorization")
|
||||
// if authHeader == "" {
|
||||
// c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization header missing"})
|
||||
// return
|
||||
// }
|
||||
// AuthMiddleware validates Bearer token in Authorization header
|
||||
func AuthJWTMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization header missing"})
|
||||
return
|
||||
}
|
||||
|
||||
// parts := strings.SplitN(authHeader, " ", 2)
|
||||
// if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
|
||||
// c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization header format must be Bearer {token}"})
|
||||
// return
|
||||
// }
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization header format must be Bearer {token}"})
|
||||
return
|
||||
}
|
||||
|
||||
// token := parts[1]
|
||||
// // For now, use a static token for validation. Replace with your logic.
|
||||
// const validToken = "your-static-token"
|
||||
token := parts[1]
|
||||
// For now, use a static token for validation. Replace with your logic.
|
||||
const validToken = "your-static-token"
|
||||
|
||||
// if token != validToken {
|
||||
// c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
|
||||
// return
|
||||
// }
|
||||
if token != validToken {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
|
||||
return
|
||||
}
|
||||
|
||||
// c.Next()
|
||||
// }
|
||||
// }
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
77
internal/middleware/jwt_middleware.go
Normal file
77
internal/middleware/jwt_middleware.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"api-service/internal/services"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// JWTAuthMiddleware validates JWT tokens generated by our auth service
|
||||
func JWTAuthMiddleware(authService *services.AuthService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization header missing"})
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization header format must be Bearer {token}"})
|
||||
return
|
||||
}
|
||||
|
||||
tokenString := parts[1]
|
||||
|
||||
// Validate token
|
||||
claims, err := authService.ValidateToken(tokenString)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Set user info in context
|
||||
c.Set("user_id", claims.UserID)
|
||||
c.Set("username", claims.Username)
|
||||
c.Set("email", claims.Email)
|
||||
c.Set("role", claims.Role)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// OptionalAuthMiddleware allows both authenticated and unauthenticated requests
|
||||
func OptionalAuthMiddleware(authService *services.AuthService) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
// No token provided, but continue
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.SplitN(authHeader, " ", 2)
|
||||
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
tokenString := parts[1]
|
||||
claims, err := authService.ValidateToken(tokenString)
|
||||
if err != nil {
|
||||
// Invalid token, but continue (don't abort)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
// Set user info in context
|
||||
c.Set("user_id", claims.UserID)
|
||||
c.Set("username", claims.Username)
|
||||
c.Set("email", claims.Email)
|
||||
c.Set("role", claims.Role)
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
31
internal/models/auth.go
Normal file
31
internal/models/auth.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package models
|
||||
|
||||
// LoginRequest represents the login request payload
|
||||
type LoginRequest struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
// TokenResponse represents the token response
|
||||
type TokenResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
}
|
||||
|
||||
// JWTClaims represents the JWT claims
|
||||
type JWTClaims struct {
|
||||
UserID string `json:"user_id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
// User represents a user for authentication
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Password string `json:"-"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
50
internal/models/product.go
Normal file
50
internal/models/product.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package models
|
||||
|
||||
// ProductGetResponse represents the response for GET products
|
||||
type ProductGetResponse struct {
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// ProductGetByIDResponse represents the response for GET product by ID
|
||||
type ProductGetByIDResponse struct {
|
||||
ID string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// ProductCreateRequest represents the request for creating product
|
||||
type ProductCreateRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
// Add more fields as needed
|
||||
}
|
||||
|
||||
// ProductCreateResponse represents the response for creating product
|
||||
type ProductCreateResponse struct {
|
||||
ID string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// ProductUpdateRequest represents the request for updating product
|
||||
type ProductUpdateRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
// Add more fields as needed
|
||||
}
|
||||
|
||||
// ProductUpdateResponse represents the response for updating product
|
||||
type ProductUpdateResponse struct {
|
||||
ID string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// ProductDeleteResponse represents the response for deleting product
|
||||
type ProductDeleteResponse struct {
|
||||
ID string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// ErrorResponse represents an error response
|
||||
// type ErrorResponse struct {
|
||||
// Error string `json:"error"`
|
||||
// }
|
||||
@@ -3,8 +3,10 @@ package v1
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"api-service/internal/config"
|
||||
"api-service/internal/handlers"
|
||||
"api-service/internal/middleware"
|
||||
"api-service/internal/services"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
@@ -12,37 +14,64 @@ import (
|
||||
)
|
||||
|
||||
// RegisterRoutes registers all API routes for version 1
|
||||
func RegisterRoutes() *gin.Engine {
|
||||
func RegisterRoutes(cfg *config.Config) *gin.Engine {
|
||||
router := gin.New()
|
||||
|
||||
// Add middleware
|
||||
router.Use(middleware.CORSConfig())
|
||||
router.Use(middleware.ErrorHandler())
|
||||
// router.Use(middleware.AuthMiddleware()) // Added auth middleware here
|
||||
router.Use(gin.Logger())
|
||||
router.Use(gin.Recovery())
|
||||
|
||||
// Initialize services
|
||||
authService := services.NewAuthService(cfg)
|
||||
|
||||
// Swagger UI route
|
||||
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||
|
||||
// API v1 group
|
||||
v1 := router.Group("/api/v1")
|
||||
{
|
||||
router.Use(middleware.AuthMiddleware()) // Added auth middleware here
|
||||
// Public routes (no authentication required)
|
||||
// Health endpoints
|
||||
healthHandler := handlers.NewHealthHandler()
|
||||
v1.GET("/health", healthHandler.GetHealth)
|
||||
v1.GET("/", healthHandler.HelloWorld)
|
||||
|
||||
// Example endpoints
|
||||
exampleHandler := handlers.NewExampleHandler()
|
||||
v1.GET("/example", exampleHandler.GetExample)
|
||||
v1.POST("/example", exampleHandler.PostExample)
|
||||
// Authentication routes
|
||||
authHandler := handlers.NewAuthHandler(authService)
|
||||
tokenHandler := handlers.NewTokenHandler(authService)
|
||||
|
||||
// WebSocket endpoint
|
||||
v1.GET("/websocket", WebSocketHandler)
|
||||
v1.POST("/auth/login", authHandler.Login)
|
||||
v1.POST("/auth/register", authHandler.Register)
|
||||
v1.GET("/auth/me", middleware.JWTAuthMiddleware(authService), authHandler.Me)
|
||||
v1.POST("/auth/refresh", authHandler.RefreshToken)
|
||||
|
||||
v1.GET("/webservice", WebServiceHandler)
|
||||
// Token generation routes
|
||||
v1.POST("/token/generate", tokenHandler.GenerateToken)
|
||||
v1.POST("/token/generate-direct", tokenHandler.GenerateTokenDirect)
|
||||
|
||||
// Protected routes (require authentication)
|
||||
protected := v1.Group("/")
|
||||
protected.Use(middleware.JWTAuthMiddleware(authService))
|
||||
{
|
||||
// Product endpoints
|
||||
productHandler := handlers.NewProductHandler()
|
||||
protected.GET("/products", productHandler.GetProduct)
|
||||
protected.GET("/products/:id", productHandler.GetProductByID)
|
||||
protected.POST("/products", productHandler.CreateProduct)
|
||||
protected.PUT("/products/:id", productHandler.UpdateProduct)
|
||||
protected.DELETE("/products/:id", productHandler.DeleteProduct)
|
||||
|
||||
// Example endpoints
|
||||
exampleHandler := handlers.NewExampleHandler()
|
||||
protected.GET("/example", exampleHandler.GetExample)
|
||||
protected.POST("/example", exampleHandler.PostExample)
|
||||
|
||||
// WebSocket endpoint
|
||||
protected.GET("/websocket", WebSocketHandler)
|
||||
protected.GET("/webservice", WebServiceHandler)
|
||||
}
|
||||
}
|
||||
|
||||
return router
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/coder/websocket"
|
||||
)
|
||||
|
||||
func (s *Server) RegisterRoutes() http.Handler {
|
||||
r := gin.Default()
|
||||
|
||||
r.Use(cors.New(cors.Config{
|
||||
AllowOrigins: []string{"http://localhost:5173"}, // Add your frontend URL
|
||||
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"},
|
||||
AllowHeaders: []string{"Accept", "Authorization", "Content-Type"},
|
||||
AllowCredentials: true, // Enable cookies/auth
|
||||
}))
|
||||
|
||||
r.GET("/", s.HelloWorldHandler)
|
||||
|
||||
r.GET("/health", s.healthHandler)
|
||||
|
||||
r.GET("/websocket", s.websocketHandler)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (s *Server) HelloWorldHandler(c *gin.Context) {
|
||||
resp := make(map[string]string)
|
||||
resp["message"] = "Hello World"
|
||||
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func (s *Server) healthHandler(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, s.db.Health())
|
||||
}
|
||||
|
||||
func (s *Server) websocketHandler(c *gin.Context) {
|
||||
w := c.Writer
|
||||
r := c.Request
|
||||
socket, err := websocket.Accept(w, r, nil)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("could not open websocket: %v", err)
|
||||
_, _ = w.Write([]byte("could not open websocket"))
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
defer socket.Close(websocket.StatusGoingAway, "server closing websocket")
|
||||
|
||||
ctx := r.Context()
|
||||
socketCtx := socket.CloseRead(ctx)
|
||||
|
||||
for {
|
||||
payload := fmt.Sprintf("server timestamp: %d", time.Now().UnixNano())
|
||||
err := socket.Write(socketCtx, websocket.MessageText, []byte(payload))
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Second * 2)
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHelloWorldHandler(t *testing.T) {
|
||||
s := &Server{}
|
||||
r := gin.New()
|
||||
r.GET("/", s.HelloWorldHandler)
|
||||
// Create a test HTTP request
|
||||
req, err := http.NewRequest("GET", "/", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Create a ResponseRecorder to record the response
|
||||
rr := httptest.NewRecorder()
|
||||
// Serve the HTTP request
|
||||
r.ServeHTTP(rr, req)
|
||||
// Check the status code
|
||||
if status := rr.Code; status != http.StatusOK {
|
||||
t.Errorf("Handler returned wrong status code: got %v want %v", status, http.StatusOK)
|
||||
}
|
||||
// Check the response body
|
||||
expected := "{\"message\":\"Hello World\"}"
|
||||
if rr.Body.String() != expected {
|
||||
t.Errorf("Handler returned unexpected body: got %v want %v", rr.Body.String(), expected)
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ func NewServer() *http.Server {
|
||||
// Declare Server config
|
||||
server := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", NewServer.port),
|
||||
Handler: v1.RegisterRoutes(),
|
||||
Handler: v1.RegisterRoutes(cfg),
|
||||
IdleTimeout: time.Minute,
|
||||
ReadTimeout: 10 * time.Second,
|
||||
WriteTimeout: 30 * time.Second,
|
||||
|
||||
169
internal/services/auth_service.go
Normal file
169
internal/services/auth_service.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"api-service/internal/config"
|
||||
"api-service/internal/models"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// AuthService handles authentication logic
|
||||
type AuthService struct {
|
||||
config *config.Config
|
||||
users map[string]*models.User // In-memory user store for demo
|
||||
}
|
||||
|
||||
// NewAuthService creates a new authentication service
|
||||
func NewAuthService(cfg *config.Config) *AuthService {
|
||||
// Initialize with demo users
|
||||
users := make(map[string]*models.User)
|
||||
|
||||
// Add demo users
|
||||
users["admin"] = &models.User{
|
||||
ID: "1",
|
||||
Username: "admin",
|
||||
Email: "admin@example.com",
|
||||
Password: "$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi", // password
|
||||
Role: "admin",
|
||||
}
|
||||
|
||||
users["user"] = &models.User{
|
||||
ID: "2",
|
||||
Username: "user",
|
||||
Email: "user@example.com",
|
||||
Password: "$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi", // password
|
||||
Role: "user",
|
||||
}
|
||||
|
||||
return &AuthService{
|
||||
config: cfg,
|
||||
users: users,
|
||||
}
|
||||
}
|
||||
|
||||
// Login authenticates user and generates JWT token
|
||||
func (s *AuthService) Login(username, password string) (*models.TokenResponse, error) {
|
||||
user, exists := s.users[username]
|
||||
if !exists {
|
||||
return nil, errors.New("invalid credentials")
|
||||
}
|
||||
|
||||
// Verify password
|
||||
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid credentials")
|
||||
}
|
||||
|
||||
// Generate JWT token
|
||||
token, err := s.generateToken(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &models.TokenResponse{
|
||||
AccessToken: token,
|
||||
TokenType: "Bearer",
|
||||
ExpiresIn: 3600, // 1 hour
|
||||
}, nil
|
||||
}
|
||||
|
||||
// generateToken creates a new JWT token for the user
|
||||
func (s *AuthService) generateToken(user *models.User) (string, error) {
|
||||
// Create claims
|
||||
claims := jwt.MapClaims{
|
||||
"user_id": user.ID,
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"role": user.Role,
|
||||
"exp": time.Now().Add(time.Hour * 1).Unix(),
|
||||
"iat": time.Now().Unix(),
|
||||
}
|
||||
|
||||
// Create token
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
// Sign token with secret key
|
||||
secretKey := []byte(s.getJWTSecret())
|
||||
return token.SignedString(secretKey)
|
||||
}
|
||||
|
||||
// GenerateTokenForUser generates a JWT token for a specific user
|
||||
func (s *AuthService) GenerateTokenForUser(user *models.User) (string, error) {
|
||||
// Create claims
|
||||
claims := jwt.MapClaims{
|
||||
"user_id": user.ID,
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"role": user.Role,
|
||||
"exp": time.Now().Add(time.Hour * 1).Unix(),
|
||||
"iat": time.Now().Unix(),
|
||||
}
|
||||
|
||||
// Create token
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
|
||||
// Sign token with secret key
|
||||
secretKey := []byte(s.getJWTSecret())
|
||||
return token.SignedString(secretKey)
|
||||
}
|
||||
|
||||
// ValidateToken validates the JWT token
|
||||
func (s *AuthService) ValidateToken(tokenString string) (*models.JWTClaims, error) {
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, errors.New("unexpected signing method")
|
||||
}
|
||||
return []byte(s.getJWTSecret()), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
return nil, errors.New("invalid token")
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok {
|
||||
return nil, errors.New("invalid claims")
|
||||
}
|
||||
|
||||
return &models.JWTClaims{
|
||||
UserID: claims["user_id"].(string),
|
||||
Username: claims["username"].(string),
|
||||
Email: claims["email"].(string),
|
||||
Role: claims["role"].(string),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getJWTSecret returns the JWT secret key
|
||||
func (s *AuthService) getJWTSecret() string {
|
||||
// In production, this should come from environment variables
|
||||
return "your-secret-key-change-this-in-production"
|
||||
}
|
||||
|
||||
// RegisterUser registers a new user (for demo purposes)
|
||||
func (s *AuthService) RegisterUser(username, email, password, role string) error {
|
||||
if _, exists := s.users[username]; exists {
|
||||
return errors.New("username already exists")
|
||||
}
|
||||
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.users[username] = &models.User{
|
||||
ID: string(rune(len(s.users) + 1)),
|
||||
Username: username,
|
||||
Email: email,
|
||||
Password: string(hashedPassword),
|
||||
Role: role,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
111
tools/HANDLER.md
Normal file
111
tools/HANDLER.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Handler Generator CLI Tool
|
||||
|
||||
CLI tool untuk generate handler baru secara otomatis dengan swagger documentation.
|
||||
|
||||
## Cara Penggunaan
|
||||
|
||||
### Windows
|
||||
```bash
|
||||
# Buka terminal di folder tools
|
||||
generate.bat <nama-handler> [methods]
|
||||
|
||||
# Contoh:
|
||||
generate.bat user get post
|
||||
generate.bat product get post put delete
|
||||
```
|
||||
|
||||
### Linux/Mac
|
||||
```bash
|
||||
# Buka terminal di folder tools
|
||||
./generate.sh <nama-handler> [methods]
|
||||
|
||||
# Contoh:
|
||||
./generate.sh user get post
|
||||
./generate.sh product get post put delete
|
||||
```
|
||||
|
||||
### Langsung dengan Go
|
||||
```bash
|
||||
# Dari root project
|
||||
# .. run tools/generate-handler.go : Perintahnya
|
||||
# .. user nama module nya dan handlernya
|
||||
# .. get post put delete metod yang di gunakan
|
||||
go run tools/generate-handler.go user get post put delete
|
||||
```
|
||||
|
||||
## Method yang Tersedia
|
||||
- `get` - GET endpoint untuk list data
|
||||
- `post` - POST endpoint untuk create data
|
||||
- `put` - PUT endpoint untuk update data
|
||||
- `delete` - DELETE endpoint untuk delete data
|
||||
|
||||
## File yang Dibuat Otomatis
|
||||
|
||||
1. **Handler**: `internal/handlers/<nama>.go`
|
||||
2. **Models**: `internal/models/<nama>.go`
|
||||
3. **Routes**: Update otomatis di `internal/routes/v1/routes.go`
|
||||
|
||||
## Contoh Penggunaan
|
||||
|
||||
### 1. Generate Handler dengan GET dan POST
|
||||
```bash
|
||||
./generate.sh user get post
|
||||
```
|
||||
|
||||
### 2. Generate Handler dengan semua method
|
||||
```bash
|
||||
./generate.sh product get post put delete
|
||||
```
|
||||
|
||||
### 3. Generate Handler dengan custom method
|
||||
```bash
|
||||
./generate.sh order get post delete
|
||||
```
|
||||
|
||||
## Langkah Setelah Generate
|
||||
|
||||
1. Jalankan swagger generator:
|
||||
```bash
|
||||
swag init -g cmd/api/main.go --output cmd/api/docs
|
||||
```
|
||||
|
||||
2. Jalankan aplikasi:
|
||||
```bash
|
||||
go run cmd/api/main.go
|
||||
```
|
||||
|
||||
3. Akses swagger UI:
|
||||
```
|
||||
http://localhost:8080/swagger/index.html
|
||||
```
|
||||
|
||||
## Struktur File yang Dibuat
|
||||
|
||||
### Handler File (`internal/handlers/<nama>.go`)
|
||||
- Struct handler
|
||||
- Constructor function
|
||||
- Endpoint methods dengan swagger documentation
|
||||
- Error handling
|
||||
|
||||
### Model File (`internal/models/<nama>.go`)
|
||||
- Request models
|
||||
- Response models
|
||||
- Error response models
|
||||
|
||||
### Routes Update
|
||||
- Otomatis menambahkan routes ke `/api/v1/<nama-plural>`
|
||||
- Support parameter ID untuk endpoint spesifik
|
||||
|
||||
## Contoh Output
|
||||
|
||||
Untuk command: `./generate.sh user get post`
|
||||
|
||||
### Handler yang dibuat:
|
||||
- `GET /api/v1/users` - List users
|
||||
- `GET /api/v1/users/:id` - Get user by ID
|
||||
- `POST /api/v1/users` - Create new user
|
||||
|
||||
### Swagger Documentation
|
||||
Semua endpoint otomatis memiliki swagger documentation yang bisa diakses di:
|
||||
```
|
||||
http://localhost:8080/swagger/index.html
|
||||
364
tools/generate-handler.go
Normal file
364
tools/generate-handler.go
Normal file
@@ -0,0 +1,364 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HandlerData contains template data for handler generation
|
||||
type HandlerData struct {
|
||||
Name string
|
||||
NameLower string
|
||||
NamePlural string
|
||||
ModuleName string
|
||||
HasGet bool
|
||||
HasPost bool
|
||||
HasPut bool
|
||||
HasDelete bool
|
||||
HasParam bool
|
||||
HasRequest bool
|
||||
HasResponse bool
|
||||
Timestamp string
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Println("Usage: go run generate-handler.go <handler-name> [methods]")
|
||||
fmt.Println("Example: go run generate-handler.go user get post put delete")
|
||||
fmt.Println("Methods: get, post, put, delete (optional, default: get post)")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
handlerName := strings.Title(os.Args[1])
|
||||
methods := []string{"get", "post"}
|
||||
if len(os.Args) > 2 {
|
||||
methods = os.Args[2:]
|
||||
}
|
||||
|
||||
// Convert to lowercase for file names
|
||||
handlerLower := strings.ToLower(handlerName)
|
||||
handlerPlural := handlerLower + "s"
|
||||
|
||||
data := HandlerData{
|
||||
Name: handlerName,
|
||||
NameLower: handlerLower,
|
||||
NamePlural: handlerPlural,
|
||||
ModuleName: "api-service",
|
||||
Timestamp: time.Now().Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
|
||||
// Check which methods are requested
|
||||
for _, method := range methods {
|
||||
switch strings.ToLower(method) {
|
||||
case "get":
|
||||
data.HasGet = true
|
||||
case "post":
|
||||
data.HasPost = true
|
||||
case "put":
|
||||
data.HasPut = true
|
||||
case "delete":
|
||||
data.HasDelete = true
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we need request/response models
|
||||
data.HasRequest = data.HasPost || data.HasPut
|
||||
data.HasResponse = true
|
||||
data.HasParam = data.HasGet || data.HasPut || data.HasDelete
|
||||
|
||||
fmt.Printf("Generating handler: %s with methods: %v\n", handlerName, methods)
|
||||
|
||||
// Create directories if they don't exist
|
||||
os.MkdirAll("internal/handlers", 0755)
|
||||
os.MkdirAll("internal/models", 0755)
|
||||
|
||||
// Generate files
|
||||
generateHandlerFile(data)
|
||||
generateModelFile(data)
|
||||
updateRoutesFile(data)
|
||||
|
||||
fmt.Printf("Successfully generated handler: %s\n", handlerName)
|
||||
fmt.Println("Don't forget to run: swag init -g cmd/api/main.go")
|
||||
}
|
||||
|
||||
func generateHandlerFile(data HandlerData) {
|
||||
handlerTemplate := `package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"{{.ModuleName}}/internal/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// {{.Name}}Handler handles {{.NameLower}} services
|
||||
type {{.Name}}Handler struct{}
|
||||
|
||||
// New{{.Name}}Handler creates a new {{.Name}}Handler
|
||||
func New{{.Name}}Handler() *{{.Name}}Handler {
|
||||
return &{{.Name}}Handler{}
|
||||
}
|
||||
{{if .HasGet}}
|
||||
// Get{{.Name}} godoc
|
||||
// @Summary Get {{.NameLower}}
|
||||
// @Description Returns a list of {{.NamePlural}}
|
||||
// @Tags {{.NameLower}}
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} models.{{.Name}}GetResponse "{{.Name}} GET response"
|
||||
// @Router /api/v1/{{.NamePlural}} [get]
|
||||
func (h *{{.Name}}Handler) Get{{.Name}}(c *gin.Context) {
|
||||
response := models.{{.Name}}GetResponse{
|
||||
Message: "List of {{.NamePlural}}",
|
||||
Data: []string{"{{.Name}} 1", "{{.Name}} 2"},
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
{{end}}
|
||||
{{if .HasGet}}
|
||||
// Get{{.Name}}ByID godoc
|
||||
// @Summary Get {{.NameLower}} by ID
|
||||
// @Description Returns a single {{.NameLower}} by ID
|
||||
// @Tags {{.NameLower}}
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "{{.Name}} ID"
|
||||
// @Success 200 {object} models.{{.Name}}GetByIDResponse "{{.Name}} GET by ID response"
|
||||
// @Failure 404 {object} models.ErrorResponse "{{.Name}} not found"
|
||||
// @Router /api/v1/{{.NamePlural}}/{id} [get]
|
||||
func (h *{{.Name}}Handler) Get{{.Name}}ByID(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
response := models.{{.Name}}GetByIDResponse{
|
||||
ID: id,
|
||||
Message: "{{.Name}} details",
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
{{end}}
|
||||
{{if .HasPost}}
|
||||
// Create{{.Name}} godoc
|
||||
// @Summary Create {{.NameLower}}
|
||||
// @Description Creates a new {{.NameLower}}
|
||||
// @Tags {{.NameLower}}
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body models.{{.Name}}CreateRequest true "{{.Name}} creation request"
|
||||
// @Success 201 {object} models.{{.Name}}CreateResponse "{{.Name}} created successfully"
|
||||
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
||||
// @Router /api/v1/{{.NamePlural}} [post]
|
||||
func (h *{{.Name}}Handler) Create{{.Name}}(c *gin.Context) {
|
||||
var req models.{{.Name}}CreateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
response := models.{{.Name}}CreateResponse{
|
||||
ID: uuid.NewString(),
|
||||
Message: "{{.Name}} created successfully",
|
||||
Data: req,
|
||||
}
|
||||
c.JSON(http.StatusCreated, response)
|
||||
}
|
||||
{{end}}
|
||||
{{if .HasPut}}
|
||||
// Update{{.Name}} godoc
|
||||
// @Summary Update {{.NameLower}}
|
||||
// @Description Updates an existing {{.NameLower}}
|
||||
// @Tags {{.NameLower}}
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "{{.Name}} ID"
|
||||
// @Param request body models.{{.Name}}UpdateRequest true "{{.Name}} update request"
|
||||
// @Success 200 {object} models.{{.Name}}UpdateResponse "{{.Name}} updated successfully"
|
||||
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
||||
// @Failure 404 {object} models.ErrorResponse "{{.Name}} not found"
|
||||
// @Router /api/v1/{{.NamePlural}}/{id} [put]
|
||||
func (h *{{.Name}}Handler) Update{{.Name}}(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
var req models.{{.Name}}UpdateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
response := models.{{.Name}}UpdateResponse{
|
||||
ID: id,
|
||||
Message: "{{.Name}} updated successfully",
|
||||
Data: req,
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
{{end}}
|
||||
{{if .HasDelete}}
|
||||
// Delete{{.Name}} godoc
|
||||
// @Summary Delete {{.NameLower}}
|
||||
// @Description Deletes a {{.NameLower}} by ID
|
||||
// @Tags {{.NameLower}}
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "{{.Name}} ID"
|
||||
// @Success 200 {object} models.{{.Name}}DeleteResponse "{{.Name}} deleted successfully"
|
||||
// @Failure 404 {object} models.ErrorResponse "{{.Name}} not found"
|
||||
// @Router /api/v1/{{.NamePlural}}/{id} [delete]
|
||||
func (h *{{.Name}}Handler) Delete{{.Name}}(c *gin.Context) {
|
||||
id := c.Param("id")
|
||||
response := models.{{.Name}}DeleteResponse{
|
||||
ID: id,
|
||||
Message: "{{.Name}} deleted successfully",
|
||||
}
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
{{end}}
|
||||
`
|
||||
|
||||
writeFile("internal/handlers/"+data.NameLower+".go", handlerTemplate, data)
|
||||
}
|
||||
|
||||
func generateModelFile(data HandlerData) {
|
||||
modelTemplate := `package models
|
||||
|
||||
{{if .HasGet}}
|
||||
// {{.Name}}GetResponse represents the response for GET {{.NamePlural}}
|
||||
type {{.Name}}GetResponse struct {
|
||||
Message string {{.Backtick}}json:"message"{{.Backtick}}
|
||||
Data interface{} {{.Backtick}}json:"data"{{.Backtick}}
|
||||
}
|
||||
{{end}}
|
||||
{{if .HasGet}}
|
||||
// {{.Name}}GetByIDResponse represents the response for GET {{.NameLower}} by ID
|
||||
type {{.Name}}GetByIDResponse struct {
|
||||
ID string {{.Backtick}}json:"id"{{.Backtick}}
|
||||
Message string {{.Backtick}}json:"message"{{.Backtick}}
|
||||
}
|
||||
{{end}}
|
||||
{{if .HasPost}}
|
||||
// {{.Name}}CreateRequest represents the request for creating {{.NameLower}}
|
||||
type {{.Name}}CreateRequest struct {
|
||||
Name string {{.Backtick}}json:"name" binding:"required"{{.Backtick}}
|
||||
// Add more fields as needed
|
||||
}
|
||||
|
||||
// {{.Name}}CreateResponse represents the response for creating {{.NameLower}}
|
||||
type {{.Name}}CreateResponse struct {
|
||||
ID string {{.Backtick}}json:"id"{{.Backtick}}
|
||||
Message string {{.Backtick}}json:"message"{{.Backtick}}
|
||||
Data interface{} {{.Backtick}}json:"data"{{.Backtick}}
|
||||
}
|
||||
{{end}}
|
||||
{{if .HasPut}}
|
||||
// {{.Name}}UpdateRequest represents the request for updating {{.NameLower}}
|
||||
type {{.Name}}UpdateRequest struct {
|
||||
Name string {{.Backtick}}json:"name" binding:"required"{{.Backtick}}
|
||||
// Add more fields as needed
|
||||
}
|
||||
|
||||
// {{.Name}}UpdateResponse represents the response for updating {{.NameLower}}
|
||||
type {{.Name}}UpdateResponse struct {
|
||||
ID string {{.Backtick}}json:"id"{{.Backtick}}
|
||||
Message string {{.Backtick}}json:"message"{{.Backtick}}
|
||||
Data interface{} {{.Backtick}}json:"data"{{.Backtick}}
|
||||
}
|
||||
{{end}}
|
||||
{{if .HasDelete}}
|
||||
// {{.Name}}DeleteResponse represents the response for deleting {{.NameLower}}
|
||||
type {{.Name}}DeleteResponse struct {
|
||||
ID string {{.Backtick}}json:"id"{{.Backtick}}
|
||||
Message string {{.Backtick}}json:"message"{{.Backtick}}
|
||||
}
|
||||
{{end}}
|
||||
|
||||
// ErrorResponse represents an error response
|
||||
type ErrorResponse struct {
|
||||
Error string {{.Backtick}}json:"error"{{.Backtick}}
|
||||
}
|
||||
`
|
||||
|
||||
// Replace backtick with actual backtick
|
||||
modelTemplate = strings.ReplaceAll(modelTemplate, "{{.Backtick}}", "`")
|
||||
writeFile("internal/models/"+data.NameLower+".go", modelTemplate, data)
|
||||
}
|
||||
|
||||
func updateRoutesFile(data HandlerData) {
|
||||
routesFile := "internal/routes/v1/routes.go"
|
||||
|
||||
// Read existing routes file
|
||||
content, err := os.ReadFile(routesFile)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading routes file: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Convert to string
|
||||
routesContent := string(content)
|
||||
|
||||
// Find the place to insert new routes
|
||||
insertMarker := "\t\t// Example endpoints"
|
||||
|
||||
// Generate new routes
|
||||
newRoutes := fmt.Sprintf("\t\t// %s endpoints\n", data.Name)
|
||||
|
||||
if data.HasGet {
|
||||
newRoutes += fmt.Sprintf("\t\t%sHandler := handlers.New%sHandler()\n", data.NameLower, data.Name)
|
||||
newRoutes += fmt.Sprintf("\t\tv1.GET(\"/%s\", %sHandler.Get%s)\n", data.NamePlural, data.NameLower, data.Name)
|
||||
}
|
||||
|
||||
if data.HasGet {
|
||||
newRoutes += fmt.Sprintf("\t\tv1.GET(\"/%s/:id\", %sHandler.Get%sByID)\n", data.NamePlural, data.NameLower, data.Name)
|
||||
}
|
||||
|
||||
if data.HasPost {
|
||||
if !data.HasGet {
|
||||
newRoutes += fmt.Sprintf("\t\t%sHandler := handlers.New%sHandler()\n", data.NameLower, data.Name)
|
||||
}
|
||||
newRoutes += fmt.Sprintf("\t\tv1.POST(\"/%s\", %sHandler.Create%s)\n", data.NamePlural, data.NameLower, data.Name)
|
||||
}
|
||||
|
||||
if data.HasPut {
|
||||
newRoutes += fmt.Sprintf("\t\tv1.PUT(\"/%s/:id\", %sHandler.Update%s)\n", data.NamePlural, data.NameLower, data.Name)
|
||||
}
|
||||
|
||||
if data.HasDelete {
|
||||
newRoutes += fmt.Sprintf("\t\tv1.DELETE(\"/%s/:id\", %sHandler.Delete%s)\n", data.NamePlural, data.NameLower, data.Name)
|
||||
}
|
||||
|
||||
newRoutes += "\n"
|
||||
|
||||
// Insert new routes after the marker
|
||||
newContent := strings.Replace(routesContent, insertMarker, insertMarker+"\n"+newRoutes, 1)
|
||||
|
||||
// Write back to file
|
||||
err = os.WriteFile(routesFile, []byte(newContent), 0644)
|
||||
if err != nil {
|
||||
fmt.Printf("Error writing routes file: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func writeFile(filename, templateStr string, data HandlerData) {
|
||||
tmpl, err := template.New("template").Parse(templateStr)
|
||||
if err != nil {
|
||||
fmt.Printf("Error parsing template: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
fmt.Printf("Error creating file %s: %v\n", filename, err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
err = tmpl.Execute(file, data)
|
||||
if err != nil {
|
||||
fmt.Printf("Error executing template: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Generated: %s\n", filename)
|
||||
}
|
||||
28
tools/generate.bat
Normal file
28
tools/generate.bat
Normal file
@@ -0,0 +1,28 @@
|
||||
@echo off
|
||||
REM Handler Generator Script for Windows
|
||||
REM Usage: generate.bat <handler-name> [methods]
|
||||
|
||||
if "%~1"=="" (
|
||||
echo Usage: generate.bat ^<handler-name^> [methods]
|
||||
echo Example: generate.bat user get post put delete
|
||||
echo Methods: get, post, put, delete (optional, default: get post)
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
set HANDLER_NAME=%~1
|
||||
shift
|
||||
|
||||
set METHODS=%*
|
||||
if "%METHODS%"=="" set METHODS=get post
|
||||
|
||||
echo Generating handler: %HANDLER_NAME% with methods: %METHODS%
|
||||
echo.
|
||||
|
||||
cd /d "%~dp0.."
|
||||
go run tools/generate-handler.go %HANDLER_NAME% %METHODS%
|
||||
|
||||
echo.
|
||||
echo Handler generated successfully!
|
||||
echo Don't forget to run: swag init -g cmd/api/main.go
|
||||
pause
|
||||
33
tools/generate.sh
Normal file
33
tools/generate.sh
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
# Handler Generator Script for Unix/Linux/Mac
|
||||
# Usage: ./generate.sh <handler-name> [methods]
|
||||
|
||||
set -e
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 <handler-name> [methods]"
|
||||
echo "Example: $0 user get post put delete"
|
||||
echo "Methods: get, post, put, delete (optional, default: get post)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
HANDLER_NAME=$1
|
||||
shift
|
||||
|
||||
METHODS=$@
|
||||
if [ -z "$METHODS" ]; then
|
||||
METHODS="get post"
|
||||
fi
|
||||
|
||||
echo "Generating handler: $HANDLER_NAME with methods: $METHODS"
|
||||
echo
|
||||
|
||||
# Change to project root directory
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Run the generator
|
||||
go run tools/generate-handler.go "$HANDLER_NAME" $METHODS
|
||||
|
||||
echo
|
||||
echo "Handler generated successfully!"
|
||||
echo "Don't forget to run: swag init -g cmd/api/main.go"
|
||||
Reference in New Issue
Block a user