Generete Module Handler

This commit is contained in:
2025-08-15 15:07:38 +07:00
parent a64cbf4438
commit 66f6d0a83b
24 changed files with 1977 additions and 2369 deletions

289
LAYOUT.md Normal file
View File

@@ -0,0 +1,289 @@
# 📊 API Service Project Layout
## 🎯 Project Overview Dashboard
### 📈 Key Metrics
- **Language**: Go 1.24.4
- **Framework**: Gin 1.10.1
- **Database**: PostgreSQL with GORM
- **Authentication**: JWT + Keycloak
- **Documentation**: Swagger/OpenAPI 3.0
- **Container**: Docker & Docker Compose
### 🏗️ Architecture Overview
```
api-service/
├── cmd/
│ └── api/
│ └── main.go
├── internal/
│ ├── config/ # Config loader
│ ├── server/ # HTTP server setup + routes binding
│ ├── handler/ # HTTP handlers (controller layer)
│ ├── service/ # Business logic
│ ├── repository/ # DB access layer
│ ├── model/ # App/domain models
│ └── middleware/ # HTTP middlewares
├── pkg/ # Reusable packages independent of business logic
│ ├── logger/
│ ├── utils/
│ └── validator/
├── scripts/ # Deployment & build automation
└── docs/ # Documentation (incl. swagger)
```
## 🚀 Development Environment Layout
### 📋 Prerequisites Setup
```bash
# Required tools
- Go 1.24.4+
- PostgreSQL 12+
- Docker & Docker Compose
- Keycloak Server (optional)
# Development tools
- Air (hot reload)
- Swag (Swagger docs)
- Make (build automation)
```
### 🔧 Configuration Files Layout
```
project-root/
├── .env # Environment variables
├── .air.toml # Air configuration
├── Makefile # Build commands
├── docker-compose.yml # Docker services
├── Dockerfile # Container image
└── .goreleaser.yml # Release configuration
```
## 📚 API Documentation Layout
### 🌐 Base URL Structure
```
http://localhost:8080/api/v1
```
### 📖 Endpoint Categories
#### 🔍 Health & Monitoring
- `GET /api/v1/health` - Health check
- `GET /api/v1/` - Hello world
- `GET /api/v1/metrics` - Application metrics
#### 🔐 Authentication Endpoints
- `POST /api/v1/auth/login` - User login
- `POST /api/v1/auth/register` - User registration
- `POST /api/v1/auth/refresh` - Token refresh
- `POST /api/v1/auth/logout` - User logout
#### 👤 User Management
- `GET /api/v1/users/profile` - Get user profile
- `PUT /api/v1/users/profile` - Update user profile
- `GET /api/v1/users` - List users (admin)
- `GET /api/v1/users/:id` - Get user by ID
#### 📦 Product Management
- `GET /api/v1/products` - List products
- `POST /api/v1/products` - Create product
- `GET /api/v1/products/:id` - Get product
- `PUT /api/v1/products/:id` - Update product
- `DELETE /api/v1/products/:id` - Delete product
#### 🔄 Real-time Features
- `GET /api/v1/websocket` - WebSocket connection
- `GET /api/v1/webservice` - Web service endpoint
### 📊 Swagger Documentation
- **Interactive UI**: http://localhost:8080/swagger/index.html
- **OpenAPI JSON**: http://localhost:8080/swagger/doc.json
## 🐳 Docker Deployment Layout
### 🏃 Development Environment
```bash
# Start all services
make docker-run
# Services included:
- PostgreSQL database
- API service with hot reload
- pgAdmin (database management)
```
### 🚀 Production Environment
```bash
# Build production image
docker build -t api-service:prod .
# Run production container
docker run -d \
-p 8080:8080 \
--env-file .env \
--name api-service \
api-service:prod
```
### 📦 Docker Compose Services
```yaml
services:
postgres:
image: postgres:15-alpine
environment:
POSTGRES_DB: api_service
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
api:
build: .
ports:
- "8080:8080"
depends_on:
- postgres
environment:
- BLUEPRINT_DB_HOST=postgres
volumes:
- .:/app
command: air -c .air.toml
```
## 🛠️ Development Workflow Layout
### 🔄 Daily Development Cycle
```bash
# 1. Start development environment
make watch
# 2. Run tests
make test
# 3. Check code quality
make lint
# 4. Build for production
make build
```
### 🧪 Testing Strategy
```
tests/
├── unit/ # Unit tests
├── integration/ # Integration tests
├── e2e/ # End-to-end tests
└── fixtures/ # Test data
```
### 📊 Monitoring & Logging
- **Health Check**: `/api/v1/health`
- **Metrics**: Prometheus metrics
- **Logging**: Structured JSON logs
- **Tracing**: Distributed tracing support
## 📁 File Structure Detailed Layout
### 📄 Configuration Files
```
.env.example # Environment template
config/
├── config.go # Configuration struct
├── config_test.go # Configuration tests
└── validation.go # Config validation
```
### 🗄️ Database Layout
```
database/
├── migrations/ # Database migrations
├── seeds/ # Seed data
├── models/ # GORM models
└── repositories/ # Data access layer
```
### 🎯 Handler Layout
```
handlers/
├── auth.go # Authentication handlers
├── user.go # User management handlers
├── product.go # Product handlers
├── health.go # Health check handlers
└── middleware/ # HTTP middleware
├── auth.go # JWT middleware
├── cors.go # CORS middleware
└── logger.go # Logging middleware
```
### 🌐 Route Layout
```
routes/
├── v1/
│ ├── routes.go # Route definitions
│ ├── auth_routes.go # Auth routes
│ ├── user_routes.go # User routes
│ └── product_routes.go # Product routes
└── middleware.go # Route middleware
```
## 🚀 Quick Start Commands
### 🏃 Development
```bash
# Clone and setup
git clone <repo-url>
cd api-service
cp .env.example .env
make docker-run
# Start development
make watch
```
### 🧪 Testing
```bash
# Run all tests
make all
# Run specific test
make test
make itest
```
### 🚀 Production
```bash
# Build and run
make build
./main.exe
```
## 📊 Performance Monitoring Layout
### 📈 Metrics Collection
- **Request Duration**: Track API response times
- **Error Rates**: Monitor error frequencies
- **Database Performance**: Query execution times
- **Memory Usage**: Application memory consumption
### 🔍 Health Checks
- **Database Connectivity**: PostgreSQL connection status
- **External Services**: Keycloak availability
- **Resource Usage**: CPU and memory utilization
## 🎯 Next Steps
1. **Setup Environment**: Copy `.env.example` to `.env`
2. **Start Database**: Run `make docker-run`
3. **Start Development**: Run `make watch`
4. **Test API**: Visit `http://localhost:8080/swagger/index.html`
5. **Run Tests**: Execute `make all`
## 📞 Support & Resources
- **Documentation**: Check `/docs` folder
- **API Testing**: Use Swagger UI at `/swagger/index.html`
- **Database**: Access pgAdmin at `http://localhost:5050`
- **Issues**: Create GitHub issues for bugs/features

View File

@@ -1,833 +0,0 @@
// Code generated by swaggo/swag. DO NOT EDIT.
package docs
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"termsOfService": "http://swagger.io/terms/",
"contact": {
"name": "API Support",
"url": "http://www.swagger.io/support",
"email": "support@swagger.io"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/": {
"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/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",
"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"
}
}
}
}
},
"/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",
"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.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"
}
}
}
}
}`
// 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)
}

View File

@@ -1,814 +0,0 @@
{
"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/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",
"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"
}
}
}
}
},
"/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",
"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.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"
}
}
}
}
}

View File

@@ -1,534 +0,0 @@
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.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:
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/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:
- 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
/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:
- 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"

View File

@@ -11,7 +11,7 @@ import (
"api-service/internal/server"
_ "api-service/cmd/api/docs"
_ "api-service/docs"
)
// @title API Service

View File

@@ -24,6 +24,7 @@ const docTemplate = `{
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/": {
"get": {
"description": "Returns a hello world message",
@@ -47,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",
@@ -294,6 +485,104 @@ const docTemplate = `{
}
}
},
"/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",
@@ -401,6 +690,21 @@ const docTemplate = `{
}
}
},
"models.LoginRequest": {
"type": "object",
"required": [
"password",
"username"
],
"properties": {
"password": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"models.ProductCreateRequest": {
"type": "object",
"required": [
@@ -477,6 +781,37 @@ const docTemplate = `{
"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"
}
}
}
}
}`

View File

@@ -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",
@@ -292,6 +482,104 @@
}
}
},
"/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",
@@ -399,6 +687,21 @@
}
}
},
"models.LoginRequest": {
"type": "object",
"required": [
"password",
"username"
],
"properties": {
"password": {
"type": "string"
},
"username": {
"type": "string"
}
}
},
"models.ProductCreateRequest": {
"type": "object",
"required": [
@@ -475,6 +778,37 @@
"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"
}
}
}
}
}

View File

@@ -49,6 +49,16 @@ definitions:
version:
type: string
type: object
models.LoginRequest:
properties:
password:
type: string
username:
type: string
required:
- password
- username
type: object
models.ProductCreateRequest:
properties:
name:
@@ -99,6 +109,26 @@ definitions:
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:
@@ -128,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:
@@ -291,6 +444,71 @@ paths:
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:

View File

@@ -1,8 +1,8 @@
package handlers
import (
"api-service/internal/models"
"api-service/internal/services"
models "api-service/internal/models/auth"
services "api-service/internal/services/auth"
"net/http"
"github.com/gin-gonic/gin"

View File

@@ -1,8 +1,8 @@
package handlers
import (
"api-service/internal/models"
"api-service/internal/services"
models "api-service/internal/models/auth"
services "api-service/internal/services/auth"
"net/http"
"github.com/gin-gonic/gin"

View File

@@ -0,0 +1,121 @@
package handlers
import (
"net/http"
"api-service/internal/models/employee"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// EmployeeHandler handles employee services
type EmployeeHandler struct{}
// NewEmployeeHandler creates a new EmployeeHandler
func NewEmployeeHandler() *EmployeeHandler {
return &%!s(MISSING)Handler{}
}
// GetEmployee godoc
// @Summary Get employee
// @Description Returns a list of employees
// @Tags employee
// @Accept json
// @Produce json
// @Success 200 {object} employee.EmployeeGetResponse "Employee GET response"
// @Router /api/v1/employees [get]
func (h *EmployeeHandler) GetEmployee(c *gin.Context) {
response := employee.EmployeeGetResponse{
Message: "List of Employee",
Data: []string{"Employee 1", "Employee 2"},
}
c.JSON(http.StatusOK, response)
}
// GetemployeeByID godoc
// @Summary Get employee by ID
// @Description Returns a single employee by ID
// @Tags Employee
// @Accept json
// @Produce json
// @Param id path string true "employee ID"
// @Success 200 {object} Employee.EmployeeGetByIDResponse "employee GET by ID response"
// @Failure 404 {object} Employee.ErrorResponse "Employee not found"
// @Router /api/v1/employee/{id} [get]
func (h *EmployeeHandler) GetemployeeByID(c *gin.Context) {
id := c.Param("id")
response := Employee.%!s(MISSING)GetByIDResponse{
ID: id,
Message: "%!s(MISSING) details",
}
c.JSON(http.StatusOK, response)
}
// CreateEmployee godoc
// @Summary Create employee
// @Description Creates a new employee
// @Tags employee
// @Accept json
// @Produce json
// @Param request body employee.EmployeeCreateRequest true "Employee creation request"
// @Success 201 {object} employee.EmployeeCreateResponse "Employee created successfully"
// @Failure 400 {object} employee.ErrorResponse "Bad request"
// @Router /api/v1/Employee [post]
func (h *EmployeeHandler) Createemployee(c *gin.Context) {
var req Employee.EmployeeCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
response := %!s(MISSING).%!s(MISSING)CreateResponse{
ID: uuid.NewString(),
Message: "%!s(MISSING) created successfully",
Data: req,
}
c.JSON(http.StatusCreated, response)
}
// UpdateEmployee godoc
// @Summary Update employee
// @Description Updates an existing employee
// @Tags employee
// @Accept json
// @Produce json
// @Param id path string true "Employee ID"
// @Param request body employee.EmployeeUpdateRequest true "Employee update request"
// @Success 200 {object} Employee.employeeUpdateResponse "Employee updated successfully"
// @Failure 400 {object} employee.ErrorResponse "Bad request"
// @Failure 404 {object} Employee.ErrorResponse "employee not found"
// @Router /api/v1/Employee/{id} [put]
func (h *EmployeeHandler) UpdateEmployee(c *gin.Context) {
id := c.Param("id")
var req Employee.%!s(MISSING)UpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
response := %!s(MISSING).%!s(MISSING)UpdateResponse{
ID: id,
Message: "%!s(MISSING) updated successfully",
Data: req,
}
c.JSON(http.StatusOK, response)
}
// DeleteEmployee godoc
// @Summary Delete employee
// @Description Deletes a employee by ID
// @Tags employee
// @Accept json
// @Produce json
// @Param id path string true "Employee ID"
// @Success 200 {object} employee.EmployeeDeleteResponse "Employee deleted successfully"
// @Failure 404 {object} employee.ErrorResponse "Employee not found"
// @Router /api/v1/Employee/{id} [delete]
func (h *employeeHandler) DeleteEmployee(c *gin.Context) {
id := c.Param("id")
response := Employee.%!s(MISSING)DeleteResponse{
ID: id,
Message: "%!s(MISSING) deleted successfully",
}
c.JSON(http.StatusOK, response)
}

View File

@@ -1,7 +1,7 @@
package middleware
import (
"api-service/internal/services"
services "api-service/internal/services/auth"
"net/http"
"strings"

View File

@@ -0,0 +1,46 @@
package employee
// EmployeeGetResponse represents the response for GET employees
type EmployeeGetResponse struct {
Message string `json:"message"`
Data interface{} `json:"data"`
}
// EmployeeGetByIDResponse represents the response for GET employee by ID
type EmployeeGetByIDResponse struct {
ID string `json:"id"`
Message string `json:"message"`
}
%!(EXTRA string=employee, string=Employee)// EmployeeCreateRequest represents the request for creating employee
type EmployeeCreateRequest struct {
Name string `json:"name" binding:"required"`
// Add more fields as needed
}
// employeeCreateResponse represents the response for creating Employee
type employeeCreateResponse struct {
ID string `json:"id"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
%!(EXTRA string=Employee)// EmployeeUpdateRequest represents the request for updating employee
type EmployeeUpdateRequest struct {
Name string `json:"name" binding:"required"`
// Add more fields as needed
}
// employeeUpdateResponse represents the response for updating Employee
type employeeUpdateResponse struct {
ID string `json:"id"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
%!(EXTRA string=Employee)// EmployeeDeleteResponse represents the response for deleting employee
type EmployeeDeleteResponse struct {
ID string `json:"id"`
Message string `json:"message"`
}
%!(EXTRA string=employee)// ErrorResponse represents an error response
type ErrorResponse struct {
Error string `json:"error"`
}

View File

@@ -0,0 +1,42 @@
package model
import "time"
// Product represents the product domain model
type Product struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Price float64 `json:"price"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// ProductCreateRequest represents the request for creating a product
type ProductCreateRequest struct {
Name string `json:"name" binding:"required"`
Description string `json:"description"`
Price float64 `json:"price" binding:"required,gt=0"`
}
// ProductUpdateRequest represents the request for updating a product
type ProductUpdateRequest struct {
Name string `json:"name" binding:"required"`
Description string `json:"description"`
Price float64 `json:"price" binding:"required,gt=0"`
}
// ProductResponse represents the response for product operations
type ProductResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Price float64 `json:"price"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// ProductsResponse represents the response for listing products
type ProductsResponse struct {
Data []*Product `json:"data"`
}

View File

@@ -0,0 +1,131 @@
package product
import (
"context"
"database/sql"
model "api-service/internal/models/product"
)
// Repository defines the interface for product data operations
type Repository interface {
Create(ctx context.Context, product *model.Product) error
GetByID(ctx context.Context, id string) (*model.Product, error)
GetAll(ctx context.Context) ([]*model.Product, error)
Update(ctx context.Context, product *model.Product) error
Delete(ctx context.Context, id string) error
}
// repository implements the Repository interface
type repository struct {
db *sql.DB
}
// NewRepository creates a new product repository
func NewRepository(db *sql.DB) Repository {
return &repository{db: db}
}
// Create adds a new product to the database
func (r *repository) Create(ctx context.Context, product *model.Product) error {
query := `
INSERT INTO products (id, name, description, price, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?)
`
_, err := r.db.ExecContext(ctx, query,
product.ID,
product.Name,
product.Description,
product.Price,
product.CreatedAt,
product.UpdatedAt,
)
return err
}
// GetByID retrieves a product by its ID
func (r *repository) GetByID(ctx context.Context, id string) (*model.Product, error) {
query := `
SELECT id, name, description, price, created_at, updated_at
FROM products
WHERE id = ?
`
var product model.Product
err := r.db.QueryRowContext(ctx, query, id).Scan(
&product.ID,
&product.Name,
&product.Description,
&product.Price,
&product.CreatedAt,
&product.UpdatedAt,
)
if err != nil {
return nil, err
}
return &product, nil
}
// GetAll retrieves all products
func (r *repository) GetAll(ctx context.Context) ([]*model.Product, error) {
query := `
SELECT id, name, description, price, created_at, updated_at
FROM products
ORDER BY created_at DESC
`
rows, err := r.db.QueryContext(ctx, query)
if err != nil {
return nil, err
}
defer rows.Close()
var products []*model.Product
for rows.Next() {
var product model.Product
err := rows.Scan(
&product.ID,
&product.Name,
&product.Description,
&product.Price,
&product.CreatedAt,
&product.UpdatedAt,
)
if err != nil {
return nil, err
}
products = append(products, &product)
}
return products, nil
}
// Update updates an existing product
func (r *repository) Update(ctx context.Context, product *model.Product) error {
query := `
UPDATE products
SET name = ?, description = ?, price = ?, updated_at = ?
WHERE id = ?
`
_, err := r.db.ExecContext(ctx, query,
product.Name,
product.Description,
product.Price,
product.UpdatedAt,
product.ID,
)
return err
}
// Delete removes a product from the database
func (r *repository) Delete(ctx context.Context, id string) error {
query := `DELETE FROM products WHERE id = ?`
_, err := r.db.ExecContext(ctx, query, id)
return err
}

View File

@@ -4,9 +4,11 @@ import (
"net/http"
"api-service/internal/config"
"api-service/internal/handlers"
authHandlers "api-service/internal/handlers/auth"
componentHandlers "api-service/internal/handlers/component"
employeeHandlers "api-service/internal/handlers/employee"
"api-service/internal/middleware"
"api-service/internal/services"
services "api-service/internal/services/auth"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
@@ -34,13 +36,13 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
{
// Public routes (no authentication required)
// Health endpoints
healthHandler := handlers.NewHealthHandler()
healthHandler := componentHandlers.NewHealthHandler()
v1.GET("/health", healthHandler.GetHealth)
v1.GET("/", healthHandler.HelloWorld)
// Authentication routes
authHandler := handlers.NewAuthHandler(authService)
tokenHandler := handlers.NewTokenHandler(authService)
authHandler := authHandlers.NewAuthHandler(authService)
tokenHandler := authHandlers.NewTokenHandler(authService)
v1.POST("/auth/login", authHandler.Login)
v1.POST("/auth/register", authHandler.Register)
@@ -52,11 +54,20 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
v1.POST("/token/generate-direct", tokenHandler.GenerateTokenDirect)
// Protected routes (require authentication)
// Employee endpoints
employeeHandler := employeeHandlers.NewEmployeeHandler()
v1.GET("/employees", employeeHandler.GetEmployee)
v1.GET("/employees/:id", employeeHandler.GetEmployeeByID)
v1.POST("/employees", employeeHandler.CreateEmployee)
v1.PUT("/employees/:id", employeeHandler.UpdateEmployee)
v1.DELETE("/employees/:id", employeeHandler.DeleteEmployee)
protected := v1.Group("/")
protected.Use(middleware.JWTAuthMiddleware(authService))
{
// Product endpoints
productHandler := handlers.NewProductHandler()
productHandler := componentHandlers.NewProductHandler()
protected.GET("/products", productHandler.GetProduct)
protected.GET("/products/:id", productHandler.GetProductByID)
protected.POST("/products", productHandler.CreateProduct)
@@ -64,7 +75,7 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
protected.DELETE("/products/:id", productHandler.DeleteProduct)
// Example endpoints
exampleHandler := handlers.NewExampleHandler()
exampleHandler := componentHandlers.NewExampleHandler()
protected.GET("/example", exampleHandler.GetExample)
protected.POST("/example", exampleHandler.PostExample)

View File

@@ -2,7 +2,7 @@ package services
import (
"api-service/internal/config"
"api-service/internal/models"
models "api-service/internal/models/auth"
"errors"
"time"

View File

@@ -0,0 +1,141 @@
package product
import (
"context"
"errors"
"time"
model "api-service/internal/models/product"
"api-service/internal/repository/product"
)
// Service defines the interface for product business logic
type Service interface {
CreateProduct(ctx context.Context, req *model.ProductCreateRequest) (*model.ProductResponse, error)
GetProduct(ctx context.Context, id string) (*model.ProductResponse, error)
GetAllProducts(ctx context.Context) (*model.ProductsResponse, error)
UpdateProduct(ctx context.Context, id string, req *model.ProductUpdateRequest) (*model.ProductResponse, error)
DeleteProduct(ctx context.Context, id string) error
}
// service implements the Service interface
type service struct {
repo product.Repository
}
// NewService creates a new product service
func NewService(repo product.Repository) Service {
return &service{repo: repo}
}
// CreateProduct creates a new product
func (s *service) CreateProduct(ctx context.Context, req *model.ProductCreateRequest) (*model.ProductResponse, error) {
if req.Name == "" {
return nil, errors.New("product name is required")
}
if req.Price <= 0 {
return nil, errors.New("product price must be greater than 0")
}
product := &model.Product{
ID: generateID(),
Name: req.Name,
Description: req.Description,
Price: req.Price,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
err := s.repo.Create(ctx, product)
if err != nil {
return nil, err
}
return &model.ProductResponse{
ID: product.ID,
Name: product.Name,
Description: product.Description,
Price: product.Price,
CreatedAt: product.CreatedAt,
UpdatedAt: product.UpdatedAt,
}, nil
}
// GetProduct retrieves a product by ID
func (s *service) GetProduct(ctx context.Context, id string) (*model.ProductResponse, error) {
if id == "" {
return nil, errors.New("product ID is required")
}
product, err := s.repo.GetByID(ctx, id)
if err != nil {
return nil, err
}
return &model.ProductResponse{
ID: product.ID,
Name: product.Name,
Description: product.Description,
Price: product.Price,
CreatedAt: product.CreatedAt,
UpdatedAt: product.UpdatedAt,
}, nil
}
// GetAllProducts retrieves all products
func (s *service) GetAllProducts(ctx context.Context) (*model.ProductsResponse, error) {
products, err := s.repo.GetAll(ctx)
if err != nil {
return nil, err
}
return &model.ProductsResponse{
Data: products,
}, nil
}
// UpdateProduct updates an existing product
func (s *service) UpdateProduct(ctx context.Context, id string, req *model.ProductUpdateRequest) (*model.ProductResponse, error) {
if id == "" {
return nil, errors.New("product ID is required")
}
existingProduct, err := s.repo.GetByID(ctx, id)
if err != nil {
return nil, err
}
existingProduct.Name = req.Name
existingProduct.Description = req.Description
existingProduct.Price = req.Price
existingProduct.UpdatedAt = time.Now()
err = s.repo.Update(ctx, existingProduct)
if err != nil {
return nil, err
}
return &model.ProductResponse{
ID: existingProduct.ID,
Name: existingProduct.Name,
Description: existingProduct.Description,
Price: existingProduct.Price,
CreatedAt: existingProduct.CreatedAt,
UpdatedAt: existingProduct.UpdatedAt,
}, nil
}
// DeleteProduct deletes a product
func (s *service) DeleteProduct(ctx context.Context, id string) error {
if id == "" {
return errors.New("product ID is required")
}
return s.repo.Delete(ctx, id)
}
// Helper functions
func generateID() string {
return "prod_" + time.Now().Format("20060102150405")
}

View File

@@ -27,10 +27,11 @@ generate.bat 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 <nama-handler> [methods]
# Contoh:
go run tools/generate-handler.go user get post put delete
go run tools/generate-handler.go product get post
```
## Method yang Tersedia
@@ -41,9 +42,11 @@ go run tools/generate-handler.go user get post put delete
## File yang Dibuat Otomatis
1. **Handler**: `internal/handlers/<nama>.go`
2. **Models**: `internal/models/<nama>.go`
1. **Handler**: `internal/handlers/<nama>/<nama>.go`
2. **Models**: `internal/models/<nama>/<nama>.go`
3. **Routes**: Update otomatis di `internal/routes/v1/routes.go`
- **<nama>**: `Nama Directori`
- **<nama>.go**: `Nama file class`
## Contoh Penggunaan
@@ -66,7 +69,7 @@ go run tools/generate-handler.go user get post put delete
1. Jalankan swagger generator:
```bash
swag init -g cmd/api/main.go --output cmd/api/docs
swag init -g cmd/api/main.go
```
2. Jalankan aplikasi:
@@ -81,20 +84,23 @@ http://localhost:8080/swagger/index.html
## Struktur File yang Dibuat
### Handler File (`internal/handlers/<nama>.go`)
### Handler File (`internal/handlers/<nama>/<nama>.go`)
- Struct handler
- Constructor function
- Endpoint methods dengan swagger documentation
- Error handling
- Import models dari `api-service/internal/models/<nama>`
### Model File (`internal/models/<nama>.go`)
### Model File (`internal/models/<nama>/<nama>.go`)
- Request models
- Response models
- Error response models
- Package name sesuai dengan nama handler
### Routes Update
- Otomatis menambahkan routes ke `/api/v1/<nama-plural>`
- Support parameter ID untuk endpoint spesifik
- Menggunakan componentHandlers untuk handler baru
## Contoh Output
@@ -105,7 +111,38 @@ Untuk command: `./generate.sh user get post`
- `GET /api/v1/users/:id` - Get user by ID
- `POST /api/v1/users` - Create new user
### Struktur Direktori:
```
internal/
├── handlers/
│ └── user/
│ └── user.go
├── models/
│ └── user/
│ └── user.go
```
### Swagger Documentation
Semua endpoint otomatis memiliki swagger documentation yang bisa diakses di:
```
http://localhost:8080/swagger/index.html
```
## Tips Penggunaan
1. **Nama Handler**: Gunakan nama singular (user, product, order)
2. **Method**: Pilih method sesuai kebutuhan CRUD
3. **Custom Fields**: Edit file models yang dibuat untuk menambahkan custom fields
4. **Service Layer**: Tambahkan service layer untuk business logic yang kompleks
5. **Repository Layer**: Tambahkan repository layer untuk database operations
## Troubleshooting
### Jika generate gagal:
- Pastikan berada di root project
- Pastikan file `internal/routes/v1/routes.go` ada
- Pastikan permission untuk menulis file
### Jika routes tidak muncul:
- Cek file `internal/routes/v1/routes.go` untuk duplikasi nama handler
- Pastikan tidak ada nama handler yang sama dengan yang sudah ada

View File

@@ -3,8 +3,8 @@ package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"text/template"
"time"
)
@@ -26,9 +26,9 @@ type HandlerData struct {
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: go run generate-handler.go <handler-name> [methods]")
fmt.Println("Usage: go run generate-handler.go <nama-handler> [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)")
fmt.Println("Example: go run generate-handler.go product get post")
os.Exit(1)
}
@@ -71,217 +71,267 @@ func main() {
fmt.Printf("Generating handler: %s with methods: %v\n", handlerName, methods)
// Create directories with proper structure
handlerDir := filepath.Join("internal", "handlers", handlerLower)
modelDir := filepath.Join("internal", "models", handlerLower)
// Create directories if they don't exist
os.MkdirAll("internal/handlers", 0755)
os.MkdirAll("internal/models", 0755)
dirs := []string{
handlerDir,
modelDir,
}
for _, dir := range dirs {
if err := os.MkdirAll(dir, 0755); err != nil {
fmt.Printf("Error creating directory %s: %v\n", dir, err)
os.Exit(1)
}
fmt.Printf("Created directory: %s\n", dir)
}
// Generate files
generateHandlerFile(data)
generateModelFile(data)
generateHandlerFile(data, handlerDir)
generateModelFile(data, modelDir)
updateRoutesFile(data)
fmt.Printf("Successfully generated handler: %s\n", handlerName)
fmt.Println("Don't forget to run: swag init -g cmd/api/main.go")
fmt.Println("Don't forget to:")
fmt.Println("1. Run: swag init -g cmd/api/main.go")
fmt.Println("2. Update your service layer if needed")
fmt.Println("3. Add repository layer if required")
}
func generateHandlerFile(data HandlerData) {
handlerTemplate := `package handlers
func generateHandlerFile(data HandlerData, handlerDir string) {
handlerContent := fmt.Sprintf(`package handlers
import (
"net/http"
"strings"
"{{.ModuleName}}/internal/models"
"api-service/internal/models/%s"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// {{.Name}}Handler handles {{.NameLower}} services
type {{.Name}}Handler struct{}
// %sHandler handles %s services
type %sHandler 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}}
// New%sHandler creates a new %sHandler
func New%sHandler() *%sHandler {
return &%sHandler{}
}`, data.NameLower, data.Name, data.NameLower, data.Name, data.Name, data.Name, data.Name, data.Name)
// Add methods based on requested operations
var methodsContent string
if data.HasGet {
methodsContent += fmt.Sprintf(`
// Get%s godoc
// @Summary Get %s
// @Description Returns a list of %s
// @Tags %s
// @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"},
// @Success 200 {object} %s.%sGetResponse "%s GET response"
// @Router /api/v1/%s [get]
func (h *%sHandler) Get%s(c *gin.Context) {
response := %s.%sGetResponse{
Message: "List of %s",
Data: []string{"%s 1", "%s 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}}
// Get%sByID godoc
// @Summary Get %s by ID
// @Description Returns a single %s by ID
// @Tags %s
// @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) {
// @Param id path string true "%s ID"
// @Success 200 {object} %s.%sGetByIDResponse "%s GET by ID response"
// @Failure 404 {object} %s.ErrorResponse "%s not found"
// @Router /api/v1/%s/{id} [get]
func (h *%sHandler) Get%sByID(c *gin.Context) {
id := c.Param("id")
response := models.{{.Name}}GetByIDResponse{
response := %s.%sGetByIDResponse{
ID: id,
Message: "{{.Name}} details",
Message: "%s details",
}
c.JSON(http.StatusOK, response)
}
{{end}}
{{if .HasPost}}
// Create{{.Name}} godoc
// @Summary Create {{.NameLower}}
// @Description Creates a new {{.NameLower}}
// @Tags {{.NameLower}}
}`,
data.Name, data.NameLower, data.NamePlural, data.NameLower,
data.NameLower, data.Name, data.Name, data.NamePlural,
data.Name, data.Name, data.NameLower, data.Name, data.Name, data.Name,
data.Name, data.NameLower, data.NameLower, data.NameLower,
data.Name, data.NameLower, data.Name, data.Name, data.NameLower,
data.Name, data.Name, data.NameLower, data.Name, data.NameLower, data.Name)
}
if data.HasPost {
methodsContent += fmt.Sprintf(`
// Create%s godoc
// @Summary Create %s
// @Description Creates a new %s
// @Tags %s
// @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
// @Param request body %s.%sCreateRequest true "%s creation request"
// @Success 201 {object} %s.%sCreateResponse "%s created successfully"
// @Failure 400 {object} %s.ErrorResponse "Bad request"
// @Router /api/v1/%s [post]
func (h *%sHandler) Create%s(c *gin.Context) {
var req %s.%sCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
response := models.{{.Name}}CreateResponse{
response := %s.%sCreateResponse{
ID: uuid.NewString(),
Message: "{{.Name}} created successfully",
Message: "%s 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}}
}`,
data.Name, data.NameLower, data.NameLower, data.NameLower,
data.NameLower, data.Name, data.Name, data.NameLower, data.Name,
data.Name, data.NameLower, data.Name, data.Name, data.NameLower,
data.Name, data.Name)
}
if data.HasPut {
methodsContent += fmt.Sprintf(`
// Update%s godoc
// @Summary Update %s
// @Description Updates an existing %s
// @Tags %s
// @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) {
// @Param id path string true "%s ID"
// @Param request body %s.%sUpdateRequest true "%s update request"
// @Success 200 {object} %s.%sUpdateResponse "%s updated successfully"
// @Failure 400 {object} %s.ErrorResponse "Bad request"
// @Failure 404 {object} %s.ErrorResponse "%s not found"
// @Router /api/v1/%s/{id} [put]
func (h *%sHandler) Update%s(c *gin.Context) {
id := c.Param("id")
var req models.{{.Name}}UpdateRequest
var req %s.%sUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
response := models.{{.Name}}UpdateResponse{
response := %s.%sUpdateResponse{
ID: id,
Message: "{{.Name}} updated successfully",
Message: "%s 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}}
}`,
data.Name, data.NameLower, data.NameLower, data.NameLower,
data.Name, data.NameLower, data.Name, data.Name, data.Name,
data.NameLower, data.Name, data.NameLower, data.Name, data.NameLower,
data.Name, data.Name, data.Name, data.Name)
}
if data.HasDelete {
methodsContent += fmt.Sprintf(`
// Delete%s godoc
// @Summary Delete %s
// @Description Deletes a %s by ID
// @Tags %s
// @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) {
// @Param id path string true "%s ID"
// @Success 200 {object} %s.%sDeleteResponse "%s deleted successfully"
// @Failure 404 {object} %s.ErrorResponse "%s not found"
// @Router /api/v1/%s/{id} [delete]
func (h *%sHandler) Delete%s(c *gin.Context) {
id := c.Param("id")
response := models.{{.Name}}DeleteResponse{
response := %s.%sDeleteResponse{
ID: id,
Message: "{{.Name}} deleted successfully",
Message: "%s deleted successfully",
}
c.JSON(http.StatusOK, response)
}
{{end}}
`
}`,
data.Name, data.NameLower, data.NameLower, data.NameLower,
data.Name, data.NameLower, data.Name, data.Name, data.NameLower,
data.Name, data.Name, data.NameLower, data.Name, data.Name)
}
writeFile("internal/handlers/"+data.NameLower+".go", handlerTemplate, data)
fullContent := handlerContent + methodsContent
handlerFile := filepath.Join(handlerDir, data.NameLower+".go")
writeFile(handlerFile, fullContent)
}
func generateModelFile(data HandlerData) {
modelTemplate := `package models
func generateModelFile(data HandlerData, modelDir string) {
modelContent := fmt.Sprintf("package %s\n\n", data.NameLower)
{{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}}
if data.HasGet {
modelContent += fmt.Sprintf(`// %sGetResponse represents the response for GET %s
type %sGetResponse struct {
Message string `+"`json:\"message\"`"+`
Data interface{} `+"`json:\"data\"`"+`
}
{{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}}
// %sGetByIDResponse represents the response for GET %s by ID
type %sGetByIDResponse struct {
ID string `+"`json:\"id\"`"+`
Message string `+"`json:\"message\"`"+`
}
{{end}}
{{if .HasPost}}
// {{.Name}}CreateRequest represents the request for creating {{.NameLower}}
type {{.Name}}CreateRequest struct {
Name string {{.Backtick}}json:"name" binding:"required"{{.Backtick}}
`, data.Name, data.NamePlural, data.Name, data.Name, data.NameLower, data.Name, data.NameLower, data.Name)
}
if data.HasPost {
modelContent += fmt.Sprintf(`// %sCreateRequest represents the request for creating %s
type %sCreateRequest struct {
Name string `+"`json:\"name\" binding:\"required\"`"+`
// 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}}
// %sCreateResponse represents the response for creating %s
type %sCreateResponse struct {
ID string `+"`json:\"id\"`"+`
Message string `+"`json:\"message\"`"+`
Data interface{} `+"`json:\"data\"`"+`
}
{{end}}
{{if .HasPut}}
// {{.Name}}UpdateRequest represents the request for updating {{.NameLower}}
type {{.Name}}UpdateRequest struct {
Name string {{.Backtick}}json:"name" binding:"required"{{.Backtick}}
`, data.Name, data.NameLower, data.Name, data.NameLower, data.Name, data.NameLower, data.Name)
}
if data.HasPut {
modelContent += fmt.Sprintf(`// %sUpdateRequest represents the request for updating %s
type %sUpdateRequest struct {
Name string `+"`json:\"name\" binding:\"required\"`"+`
// 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}}
// %sUpdateResponse represents the response for updating %s
type %sUpdateResponse struct {
ID string `+"`json:\"id\"`"+`
Message string `+"`json:\"message\"`"+`
Data interface{} `+"`json:\"data\"`"+`
}
{{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}}
`, data.Name, data.NameLower, data.Name, data.NameLower, data.Name, data.NameLower, data.Name)
}
// ErrorResponse represents an error response
if data.HasDelete {
modelContent += fmt.Sprintf(`// %sDeleteResponse represents the response for deleting %s
type %sDeleteResponse struct {
ID string `+"`json:\"id\"`"+`
Message string `+"`json:\"message\"`"+`
}
`, data.Name, data.NameLower, data.Name, data.NameLower)
}
modelContent += `// ErrorResponse represents an error response
type ErrorResponse struct {
Error string {{.Backtick}}json:"error"{{.Backtick}}
Error string ` + "`json:\"error\"`" + `
}
`
// Replace backtick with actual backtick
modelTemplate = strings.ReplaceAll(modelTemplate, "{{.Backtick}}", "`")
writeFile("internal/models/"+data.NameLower+".go", modelTemplate, data)
modelFile := filepath.Join(modelDir, data.NameLower+".go")
writeFile(modelFile, modelContent)
}
func updateRoutesFile(data HandlerData) {
@@ -294,28 +344,65 @@ func updateRoutesFile(data HandlerData) {
return
}
// Convert to string
routesContent := string(content)
// Find the place to insert new routes
insertMarker := "\t\t// Example endpoints"
// Check if import already exists
importPattern := fmt.Sprintf(`%sHandlers "api-service/internal/handlers/%s"`, data.NameLower, data.NameLower)
if !strings.Contains(routesContent, importPattern) {
// Find the import block and insert the new import
importToAdd := fmt.Sprintf("\t%sHandlers \"api-service/internal/handlers/%s\"", data.NameLower, data.NameLower)
// Find the import block end
importEndMarker := "\n)\n\n// RegisterRoutes"
if !strings.Contains(routesContent, importEndMarker) {
importEndMarker = "\n)\n\nfunc RegisterRoutes"
}
// Find the line before the closing parenthesis
lines := strings.Split(routesContent, "\n")
var newLines []string
importBlockFound := false
importAdded := false
for _, line := range lines {
newLines = append(newLines, line)
// Check if we're in the import block
if strings.Contains(line, "import (") {
importBlockFound = true
continue
}
// Check if we're at the end of import block
if importBlockFound && strings.TrimSpace(line) == ")" && !importAdded {
// Insert the new import before the closing parenthesis
newLines = newLines[:len(newLines)-1] // Remove the last line (closing parenthesis)
newLines = append(newLines, importToAdd)
newLines = append(newLines, line) // Add back the closing parenthesis
importAdded = true
}
}
if importAdded {
routesContent = strings.Join(newLines, "\n")
} else {
// Fallback to simple string replacement
if strings.Contains(routesContent, importEndMarker) {
routesContent = strings.Replace(routesContent, importEndMarker, "\n"+importToAdd+importEndMarker, 1)
}
}
}
// Generate new routes
newRoutes := fmt.Sprintf("\t\t// %s endpoints\n", data.Name)
newRoutes += fmt.Sprintf("\t\t%sHandler := %sHandlers.New%sHandler()\n", data.NameLower, data.NameLower, 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)
}
@@ -329,8 +416,17 @@ func updateRoutesFile(data HandlerData) {
newRoutes += "\n"
// Insert new routes after the marker
newContent := strings.Replace(routesContent, insertMarker, insertMarker+"\n"+newRoutes, 1)
// Find the place to insert new routes (after the protected group)
insertMarker := "\t\tprotected := v1.Group(\"/\")"
// Check if routes already exist
if strings.Contains(routesContent, fmt.Sprintf("New%sHandler", data.Name)) {
fmt.Printf("Routes for %s already exist, skipping...\n", data.Name)
return
}
// Insert new routes before the protected group
newContent := strings.Replace(routesContent, insertMarker, newRoutes+insertMarker, 1)
// Write back to file
err = os.WriteFile(routesFile, []byte(newContent), 0644)
@@ -338,27 +434,15 @@ func updateRoutesFile(data HandlerData) {
fmt.Printf("Error writing routes file: %v\n", err)
return
}
fmt.Printf("Updated routes file with %s endpoints\n", data.Name)
}
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)
func writeFile(filename, content string) {
err := os.WriteFile(filename, []byte(content), 0644)
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)
}